/*
 * Copyright (c) 2004-2005 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: dagflood.c 14310 2011-06-17 01:52:35Z peter.thomas $
 */

/*****************************************************************************/
/* Includes                                                                  */
/*****************************************************************************/

#include <sys/types.h>
#include <sys/stat.h>

#ifndef _WIN32 
#include <unistd.h>
#endif

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


/* CVS Header. */
static const char* const kCvsHeader __attribute__ ((unused)) = "$Id: dagflood.c 14310 2011-06-17 01:52:35Z peter.thomas $";
static const char* const kRevisionString = "$Revision: 14310 $";


/*****************************************************************************/
/* Macros and constants                                                      */
/*****************************************************************************/

typedef enum {
	RUN_TIMELIMIT,     /* Stop after N seconds limit */
	RUN_REPEAT        /* Stop after repeating file N times */
} repeat_mode_t;

typedef enum {
	MODE_COMMIT,
	MODE_COPY,
	MODE_PERPKT
} run_mode_t;

typedef enum {
	TIME_DIRECT,
	TIME_SCALE,
	TIME_RATE,
	TIME_BW
} time_mode_t;

#define BURST_MAX ONE_MEBI
#define BUFSIZE 256

#define KB 1024UL  
#define MB (KB * KB)
#define GB (MB *KB)

#define DEFAULT_DEVICE "dag0"
static char dagname_buf[DAGNAME_BUFSIZE] = DEFAULT_DEVICE;
static char dagname[DAGNAME_BUFSIZE];

static char timename_buf[DAGNAME_BUFSIZE] = DEFAULT_DEVICE;
static char timename[DAGNAME_BUFSIZE];

static char fname_buf[BUFSIZE];
static char mode_buf[BUFSIZE];
static char bmax_buf[BUFSIZE];
static char scale_buf[BUFSIZE];
static char rate_buf[BUFSIZE];
static char bw_buf[BUFSIZE];
static uint32_t count;
static uint32_t tlimit;

/*****************************************************************************/
/* Windows support                                                           */
/*****************************************************************************/
#if defined(_WIN32)

/* Windows doesn't have fseeko & ftello functions, we can use these equvalents.  */
/* Note: Windows also has undocumented _fseeki64 and _ftelli64 functions, but    */
/*       they are only built in the static CRT libraries.                        */
#define fseeko(fm, pos, type) _lseeki64(_fileno(fm), (pos), (type))
#define ftello(fm) _lseeki64(_fileno(fm), 0, SEEK_CUR)

#endif



/*****************************************************************************/
/* Data structures                                                           */
/*****************************************************************************/
typedef struct
{
	char * file;          /* Input file with ERF records to transmit      */
	char device[DAGNAME_BUFSIZE]; /* Which DAG device to use              */
	char timedevice[DAGNAME_BUFSIZE]; /* Which DAG device to use          */
	int settimedevice;    /* 1 => set DAG device to use for time reference*/
	int stream;
	uint32_t burst_max;   /* Maximum burst size                           */
	repeat_mode_t repeat_mode; /* Repeat mode: time or repeat based       */
	uint32_t repeat;      /* How many times to send the file, decrements  */
	uint32_t total_repeats; /* How many times to send the file            */
	run_mode_t run_mode;  /* Which API interface to use when sending      */
	uint32_t timelimit;   /* Send the file during this time (seconds)     */
	uint32_t usleep;      /* Inter burst delay (microseconds)             */
	uint32_t nsleep;      /* Inter packet delay (nanoseconds) (-r3)       */
	int flush_tx_buffer;  /* 1 => flush transmit buffer on exit           */
	int accuracy;         /* 1 => include read/copy time in inter-burst gap */
	int busywait;         /* 1 => use accurate dagutil busywait functions */
	int resync;           /* 1 => In tp mode set the first packet time to current time */
	int odirect;          /* 1 => when reading from files use O_DIRECT for speed */
	int replay;           /* 1 => set time_mode=relative, loop files      */
	time_mode_t time_mode;/* Time mode, direct, scale, rate, bw           */
	float scale;          /* timestamp delta scale factor for Replay mode */
	int pkt_rate;         /* Packet rate for Replay mode */
	uint64_t bw;          /* Bandwidth for Replay mode in bps */
	uint32_t start_time;  /* Start trigger time for TTR */
} conf_t;

typedef struct
{
#if defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))

	void * base;          /* Pointer to the mmap'd file                */
	void * pos;           /* Absolute pointer inside mmap buffer       */

#elif defined(_WIN32)

	char * base;          /* Pointer to the mmap'd file                */
	char * pos;           /* Absolute pointer inside mmap buffer       */
	HANDLE mapping;

#else
#error Compiling on an unsupported platform - please contact <support@endace.com> for assistance.
#endif 
	int fd;               /* File descriptor                           */
	off_t size;           /* File size                                 */
	FILE *fp;             /* File pointer                              */ 
} file_t;

typedef struct
{
	uint64_t wbytes;      /* Bytes written to stream buffer            */
	uint64_t old_wbytes;  /* Bytes written up to the previous loop     */
	uint32_t seconds;     /* Seconds the application has been running  */
	struct timeval start; /* Start time                                */
	struct timeval end;   /* End time                                  */
	uint64_t bytes_to_send; /* Number of bytes to write in repeat mode */
} count_t;

/* Commandline argument codes. */
enum
{
	CLA_FNAME,
	CLA_BMAX,
	CLA_DEVICE,
	CLA_TIMEDEVICE,
	CLA_HELP,
	CLA_TERMN,
	CLA_VERBOSE,
	CLA_VERSION,
	CLA_DELAY,
	CLA_NSDELAY,
	CLA_MODE,
	CLA_NFLUSH,
	CLA_COUNT,
	CLA_ACCURACY,
	CLA_BUSYWAIT,
	CLA_RESYNC,
	CLA_ODIRECT,
	CLA_REPLAY,
	CLA_SCALE,
	CLA_RATE,
	CLA_BW,
	CLA_START
};


/*****************************************************************************/
/* File-scope variables                                                      */
/*****************************************************************************/
static int uDagFd;        /* DAG file descriptor                       */
static conf_t uConfiguration; /* Configuration options                 */
static count_t uCounters; /* Counters                                  */
static char uContinue = 1;    /* Continue main loop?                   */
static uint8_t mmap_failed = 0; /* File mapping failed ?               */
static char *tx_buf1;
static char *tx_buf2;
static dag_card_ref_t uCardRef, uTimeRef;
static dag_component_t uCardRoot, uTimeRoot;
static uint32_t orig_time_mode;
static uint8_t uErfs[2] ={0,0};

/*****************************************************************************/
/* Internal routines                                                      */
/*****************************************************************************/

static void print_version(void);
static void print_usage(ClArgPtr clarg);
static void initialize_dag(void);
static void finalize_dag(void);
static uint64_t get_dag_time(void);
static int  get_time_mode(void);
static int  set_time_mode(int mode);
static int  terf_trigger_pending(void);
static int  clear_terf_trigger(void);
static int  set_terf_trigger(uint64_t value);
static int  get_port_count(void);
static int  sync_all_ports(uint64_t start_time);
static void flush_tx_buffer(void);
static void init_file (char *filename, file_t *file);
static void mmap_copy_bytes(file_t *file);
static void mmap_commit_bytes(file_t * file);
static void mmap_time_packets(file_t * file);
static void copy_bytes(const file_t *file);
static void commit_bytes(const file_t *file);
static void time_packets(const file_t *file);
void sighandler (int signal);
void init_options(void);
void report_statistics(void);
static int validate_input_configuration(const file_t* in_file);
#if defined _WIN32
void remove_mapping(file_t * file);
#endif

/*****************************************************************************/
/*** Functions                                                             ***/
/*****************************************************************************/
static void
print_usage(ClArgPtr clarg)
{
	print_version();
	printf("dagflood - Endace DAG card transmit utility.\n");
	printf("Usage: dagflood [options]\n");
	dagclarg_display_usage(clarg, stdout);
}

/*****************************************************************************/
static void
print_version(void)
{
	printf("dagflood (DAG %s) %s\n", kDagReleaseVersion, kRevisionString);
}
/*****************************************************************************/
static int 
validate_input_configuration(const file_t* in_file)
{
    /* check the burst size is 8-byte alignment */
    if (uConfiguration.burst_max % 8)
    {
        dagutil_error("burst_max is not 8 byte aligned\n");
        return -1;
    }
    /* check if the file size is byte aligned */
    if ( in_file->size % 8 )
    {
        dagutil_warning("The given file is not 8 byte aligned\n");
    }
   return 0;
}

/*****************************************************************************/
static void
initialize_dag(void)
{
	int temp;
	struct timeval maxwait;
	struct timeval poll;

	temp = dagutil_get_verbosity();
	dagutil_set_verbosity(0);

	if ( (uCardRef = dag_config_init(uConfiguration.device)) == NULL)
		dagutil_panic("dag_config_init(%s): %s\n", uConfiguration.device, strerror(errno));
	uCardRoot = dag_config_get_root_component(uCardRef);

	if (uConfiguration.settimedevice) {
		if ( (uTimeRef = dag_config_init(uConfiguration.timedevice)) == NULL)
			dagutil_panic("dag_config_init(%s): %s\n", uConfiguration.timedevice, strerror(errno));
		uTimeRoot = dag_config_get_root_component(uTimeRef);
	} else {
		uTimeRef = uCardRef;
		uTimeRoot = uCardRoot;
	}

	uDagFd = dag_config_get_card_fd(uCardRef);
	if (uDagFd < 0)
		dagutil_panic("dag_config_get_card_fd(%s): %s\n", uConfiguration.device, strerror(errno));

	dagutil_set_verbosity(temp);

	if (dag_tx_get_stream_count(uDagFd) < 1)
		dagutil_panic("dag_tx_get_stream_count(%s): The firmware loaded on this DAG card does not support transmit features.\n", uConfiguration.device);

	/* Setting reverse mode for using with soft dag from daemon side */
	if( (uConfiguration.stream & 0x1)== 0) {
		/* Setting reverse mode for using with soft dag from daemon side */
		if (dag_set_mode(uDagFd,uConfiguration.stream,DAG_REVERSE_MODE)) {
			dagutil_panic("Could not set reverse mode on %s:%d\n", uConfiguration.device, uConfiguration.stream);
		}
	} else {
		dag_set_mode(uDagFd,uConfiguration.stream,DAG_NORMAL_MODE);
	};

	/* Set time mode */
	if(uConfiguration.replay) {
		if( (orig_time_mode = get_time_mode()) < 0)
			dagutil_panic("Could not get time mode, firmware may not support TR-TERF features\n");
		if(set_time_mode(kTrTerfRelative))
			dagutil_panic("Could not set relative time mode, firmware may not support TR-TERF features\n");
	}

	if (dag_attach_stream(uDagFd, uConfiguration.stream, 0, dagutil_max(BURST_MAX, uConfiguration.burst_max)) != 0)
		dagutil_panic("dag_attach_stream(%s): %s\n", uConfiguration.device, strerror(errno));

	/*
	 * Initialise DAG Polling parameters.
	 */
	timerclear(&maxwait);
	maxwait.tv_usec = 100 * 1000; /* 100ms timeout */
	timerclear(&poll);
	poll.tv_usec = 10 * 1000; /* 10ms poll interval */

	/* For tx streams mindata=0 means non-blocking, otherwise ignored */
	dag_set_stream_poll(uDagFd, uConfiguration.stream, dag_record_size, &maxwait, &poll);

	if (dag_start_stream (uDagFd, uConfiguration.stream) < 0)
		dagutil_panic("dag_start_stream(%s): %s\n", uConfiguration.device, strerror(errno));

	if(uConfiguration.start_time) {
		clear_terf_trigger();
		if (set_terf_trigger(((uint64_t)uConfiguration.start_time)<<32 ) != EXIT_SUCCESS)
			uConfiguration.start_time = 0;
	}
}

/*****************************************************************************/
static void
finalize_dag(void)
{
	if (dag_stop_stream(uDagFd, uConfiguration.stream) < 0)
		dagutil_panic("dag_stop_stream(%s): %s\n", uConfiguration.device, strerror(errno));

	if (dag_detach_stream(uDagFd, uConfiguration.stream) != 0)
		dagutil_panic("dag_detach_stream(%s): %s\n", uConfiguration.device, strerror(errno));

	/* Clear time mode */
	if(uConfiguration.replay) {
		if(set_time_mode(orig_time_mode))
			dagutil_panic("Could not restore time mode, firmware may not support TR-TERF features\n");
	}

	if (uConfiguration.timedevice[0]) {
		dag_config_dispose(uTimeRef);
	}
	
	dag_config_dispose(uCardRef);
}

static uint64_t
get_dag_time( void )
{
	static dag_component_t duck=NULL;
	static attr_uuid_t attr=kNullAttributeUuid;
	
	if (duck == NULL)
		duck = dag_component_get_subcomponent(uTimeRoot, kComponentDUCK, 0);

	if (attr == kNullAttributeUuid)
		attr = dag_component_get_attribute_uuid(duck, kUint64AttributeDuckTimestamp);

	if (attr == kNullAttributeUuid)
		return -1;

	return dag_config_get_uint64_attribute(uTimeRef, attr);
}

static int
get_time_mode(void)
{
	static dag_component_t terf=NULL;
	static attr_uuid_t attr=kNullAttributeUuid;
	
	if (terf == NULL)
		terf = dag_component_get_subcomponent(uTimeRoot, kComponentTerf, 0);

	if (attr == kNullAttributeUuid)
		attr = dag_component_get_attribute_uuid(terf, kUint32AttributeTimeMode);

	if (attr == kNullAttributeUuid)
		return -1;

	return dag_config_get_uint32_attribute(uTimeRef, attr);
}

static int
set_time_mode(int mode) {
	static dag_component_t terf=NULL;
	static attr_uuid_t attr=kNullAttributeUuid;
	
	if (terf == NULL)
		terf = dag_component_get_subcomponent(uTimeRoot, kComponentTerf, 0);

	if (attr == kNullAttributeUuid)
		attr = dag_component_get_attribute_uuid(terf, kUint32AttributeTimeMode);

	if (attr == kNullAttributeUuid)
		return -1;

	return dag_config_set_uint32_attribute(uTimeRef, attr, mode);
}

static int
terf_trigger_pending(void)
{
	static dag_component_t terf=NULL;
	static attr_uuid_t pending_attr=kNullAttributeUuid;

	if(!terf)
		terf = dag_component_get_subcomponent(uTimeRoot, kComponentTerf, 0);
	if(!terf) {
		dagutil_panic("-S option not supported by this card/firmware.\n");
	}

	/* Verify pending */
	if(pending_attr == kNullAttributeUuid)
		pending_attr = dag_component_get_attribute_uuid(terf, kBooleanAttributeTriggerPending);

	if ( pending_attr == kNullAttributeUuid)
		dagutil_panic("-S option not supported by this card/firmware.\n");
	else
		return dag_config_get_boolean_attribute(uTimeRef, pending_attr);
}


static int
clear_terf_trigger(void)
{
	static dag_component_t terf=NULL;
	static attr_uuid_t clear_attr=kNullAttributeUuid;

	if (terf_trigger_pending()) {
		
		if (!terf)
			terf = dag_component_get_subcomponent(uTimeRoot, kComponentTerf, 0);
		if (!terf) {
			dagutil_panic("-S option not supported by this card/firmware.\n");
		}
		
		if (clear_attr == kNullAttributeUuid)
			clear_attr = dag_component_get_attribute_uuid(terf, kBooleanAttributeClearTrigger);
		
		if ( clear_attr != kNullAttributeUuid ) {
			dag_config_set_boolean_attribute(uTimeRef, clear_attr, 1);

			usleep(1000);
			
			/* verify clear */
			if (terf_trigger_pending()) {
				dagutil_panic("failed to clear pending start trigger\n");
			}
		} else {
			dagutil_panic("-S option not supported by this card/firmware.\n");
		}
	}

	return EXIT_SUCCESS;
}

static int
set_terf_trigger(uint64_t value)
{
	static dag_component_t terf=NULL;
	static attr_uuid_t timestamp_attr=kNullAttributeUuid;

	if(!terf)
		terf = dag_component_get_subcomponent(uTimeRoot, kComponentTerf, 0);
	if(!terf) {
		dagutil_panic("-S option not supported by this card/firmware.\n");
	}

	/* Set trigger time */
	if (timestamp_attr == kNullAttributeUuid)
		timestamp_attr = dag_component_get_attribute_uuid(terf, kUint64AttributeTriggerTimestamp);
	if ( timestamp_attr != kNullAttributeUuid )
		dag_config_set_uint64_attribute(uTimeRef, timestamp_attr, value);
	else
		dagutil_panic("-S option not supported by this card/firmware.\n");

	if (value != dag_config_get_uint64_attribute(uTimeRef, timestamp_attr))
		dagutil_panic("failed to set trigger time\n");
	
	/* Verify pending */	
	if (!terf_trigger_pending()) {
		dagutil_warning("Requested start time has already passed, starting immediately\n");
		return EXIT_FAILURE;
	} /* else pending, good */
	
	return EXIT_SUCCESS;
}

static int
get_port_count(void)
{
	int port_count = 0;

	port_count = dag_component_get_subcomponent_count_of_type(uCardRoot, kComponentPort);

	if (port_count < 1) {
		port_count = dag_get_interface_count(uDagFd);
	}

	return port_count;
}

/*
 * synchronise all the tx ports by sending 'fake' packets with the file
 * start timestamp out all of the ports first. This causes the firmware
 * to use the same start reference time for all ports.
 * 
 * The packets are not actually sent because the rxerror flag is set.
 */
static int
sync_all_ports(uint64_t start_time)
{
	int ports, i;
	uint8_t *rec = NULL;

	/* get port count */
	ports = get_port_count();

	/* if 1 port no need to sync */
	if (ports == 1)
		return 0;

	/* if negative then fail */
	if (ports > 1) {
		/* this could be blocking if sending to a vdag */
		while ( (uContinue) && (!rec) ) {
			rec = dag_tx_get_stream_space(uDagFd, uConfiguration.stream, 88*ports);
			if (!rec) {
				if (errno == EAGAIN)
					continue;
				else
					dagutil_panic("Couldn't get tx space\n");
			}
		}
		
		/* check if user did ctrl-c already */
		if (!uContinue)
			return -1;

		memset(rec, 0, 88*ports);

		for(i=0; i<ports; i++) {
			((dag_record_t*)rec)->ts = start_time;
			((dag_record_t*)rec)->rlen = htons(88);
			((dag_record_t*)rec)->wlen = htons(64);
			((dag_record_t*)rec)->flags.rxerror = 1;
			((dag_record_t*)rec)->flags.iface = i;
			((dag_record_t*)rec)->flags.vlen = 1;
			((dag_record_t*)rec)->type = uErfs[0];
			rec += 88;
		}
		
		if (dag_tx_stream_commit_bytes(uDagFd, uConfiguration.stream, (88*ports)) == NULL) {
			dagutil_panic("commit failed");
		}

		return 0;
	}
	return -1;
}

static void
flush_tx_buffer(void)
{
	if (1 == uConfiguration.flush_tx_buffer)
	{
		dagutil_verbose("Flushing transmit buffer (ctrl-c to exit)...\n");
		while ( (1 == uConfiguration.flush_tx_buffer) &&
			(dag_get_stream_buffer_level(uDagFd, uConfiguration.stream)> 16) )
		{
			usleep(1000);
		}

		if (1 == uConfiguration.flush_tx_buffer) {
			dagutil_verbose("Done.\n");
		} else {
			dagutil_verbose("Exiting on signal, not flushing.\n");
		}
	}
}


/*****************************************************************************/
static void
init_file (char *filename, file_t *file)
{
	struct stat file_stats;
#if defined(_WIN32)
	HANDLE hFile, hMapHandle;
#endif /* _WIN32 */

	/* Init file data structure. */
	memset(file, 0, sizeof(*file));

	/* Open file. */
#if defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))

	if (uConfiguration.odirect)
		file->fd = open(filename, O_RDONLY | O_DIRECT);
	else
		file->fd = open(filename, O_RDONLY);
		

#elif defined(_WIN32)

	file->fd = _open(filename, _O_RDONLY | _O_BINARY ,_S_IREAD);

#else
#error Compiling on an unsupported platform - please contact <support@endace.com> for assistance.
#endif /* Platform-specific code. */

	if (-1 == file->fd)
	{
	  dagutil_panic("error opening file %s: %s\n", filename, strerror(errno));
	}

	/* Get file size. */
	if ((fstat(file->fd, &file_stats)) < 0) 
		dagutil_panic("fstat: %s\n", strerror(errno));

	file->size = file_stats.st_size;

	uCounters.bytes_to_send = file_stats.st_size;
	if (uConfiguration.repeat_mode == RUN_REPEAT)
		uCounters.bytes_to_send *= uConfiguration.repeat;

#if (defined(__SVR4) && defined(__sun))
    printf("file input size: %"PRIu64" bytes\n", (long long)file->size); //FIXME : use some general solution
#elif (_WIN32)
	printf("file input size: %"PRId32" bytes\n", file->size);
#else
    printf("file input size: %"PRIu64" bytes\n", file->size);
#endif

	if (0 == file->size)
	{
		dagutil_panic("file to transmit was empty\n");
	}

	/* Mmap file, so we have it in memory. */
	if (dagutil_get_verbosity() > 0)
	{
		fprintf(stdout, "caching file... ");
	}

#if defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))
	
	file->base = MAP_FAILED;
	/* mmap length parameter is size_t */
	if (file->size < 0x80000000)
		file->base = mmap(NULL, file->size, PROT_READ, MAP_SHARED, file->fd, 0);
	if (file->base == MAP_FAILED)
	{
		mmap_failed = 1;
		file->fp = fdopen(file->fd, "rb");
                if ((tx_buf1 = dagutil_malloc_aligned(uConfiguration.burst_max)) == NULL)
			dagutil_panic("Could not allocate tx buffer\n");
                if ((tx_buf2 = dagutil_malloc_aligned(uConfiguration.burst_max)) == NULL)
			dagutil_panic("Could not allocate tx buffer\n");
		if (dagutil_get_verbosity() > 0)
		{
			fprintf(stdout, "too large\n");
			fprintf(stdout, "transmitting from file directly instead (rate will be limited to disk speed).\n");
		}
		return;
	}

#elif defined(_WIN32)
	/* mmap is done slightly differently in Windows. */

	/* Get a mappable handle to the file. */
	hFile = (HANDLE)_get_osfhandle(file->fd);

	if (hFile == INVALID_HANDLE_VALUE)
		dagutil_panic("invalid file descriptor: %s\n",strerror(errno));

	hMapHandle =  CreateFileMapping(
		hFile,              /* Handle to file */
		NULL,               /* LPSECURITY_ATTRIBUTES lpAttributes, */
		PAGE_READONLY,      /* DWORD flProtect, */
		0,                  /* DWORD dwMaximumSizeHigh, */
		(DWORD)file->size,  /* DWORD dwMaximumSizeLow, */
		"dagfloodmap"       /* LPCTSTR lpName */
		);

	if (hMapHandle == NULL)
	{
		mmap_failed = 1;
		CloseHandle(hMapHandle);
		_close(file->fd);
		return;
	}

	/* do the mapping. */
	file->base = MapViewOfFile(
		hMapHandle,     /* HANDLE hFileMappingObject, */
		FILE_MAP_READ,  /* DWORD dwDesiredAccess, */
		0,              /* DWORD dwFileOffsetHigh, */
		0,              /* DWORD dwFileOffsetLow, */
		0               /* default map entire file */
	);

	if (file->base == NULL)
	{
		mmap_failed = 1;
		CloseHandle(hMapHandle);
		_close(file->fd);
		return;
	}

	file->mapping = hMapHandle;
	
#else
#error Compiling on an unsupported platform - please contact <support@endace.com> for assistance.
#endif 

	if (dagutil_get_verbosity() > 0)
		fprintf(stdout, "done.\n");

	/* Update pointer inside file */
	file->pos = file->base; 
}

/*****************************************************************************/
static void
mmap_copy_bytes(file_t *file)
{
	uint32_t burst_length;
	unsigned long long wake_time;
	
	/* Main loop */
	while (uContinue)
	{
		if ( (uConfiguration.usleep) && (uConfiguration.accuracy) ) {
			dagutil_sleep_get_wake_time(&wake_time, uConfiguration.usleep);
		}

		/* The maximum burst length should not be greater than the one defined 
		 * as default nor the remaining bytes in the file buffer.
		 */
		burst_length = (uint32_t) dagutil_min((uint64_t) uConfiguration.burst_max, (uint64_t)(file->size + file->base - file->pos));

		/* Write to dag card (copy, so it's not zero-copy). */
		dag_tx_stream_copy_bytes (uDagFd, uConfiguration.stream, file->pos, burst_length);

		/* Advance offsets and counters. */
		uCounters.wbytes += burst_length;
		file->pos += burst_length;

		/* Keep file pointer inside boundaries. */
		if (file->pos >= file->base + file->size)
		{
			file->pos = file->base;
			uConfiguration.repeat--;
		}

		/* Should we repeat more times? */
		/* We were running dagflood with a repeat number option */
		if ((uConfiguration.repeat_mode == RUN_REPEAT) && (uConfiguration.repeat == 0))
		{
			uContinue = 0;
		}

		/* Inter burst delay. */
		if (uConfiguration.usleep) {
			if (uConfiguration.accuracy) {
				if (uConfiguration.busywait)
					dagutil_sleep_until(wake_time);
				else
					dagutil_sleep_until_nobusy(wake_time);
			} else {
				if (uConfiguration.busywait)
					dagutil_microsleep(uConfiguration.usleep);
				else
					usleep(uConfiguration.usleep);
			}
		}
	}

	/* Wait for the stream to be almost empty */
	/* This way we are sure almost all the records are transmitted when we exit. */
	flush_tx_buffer();
}

/*****************************************************************************/
static void
mmap_commit_bytes(file_t * file)
{
	uint32_t burst_length;
	void * record;
	unsigned long long wake_time;

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

	/* Main loop */
	while (uContinue)
	{
		if ( (uConfiguration.usleep) && (uConfiguration.accuracy) ) {
			dagutil_sleep_get_wake_time(&wake_time, uConfiguration.usleep);
		}

		/* The maximum burst length should not be greater than the one defined 
		 * as default nor the remaining bytes in the file buffer.
		 */
		burst_length = (uint32_t) dagutil_min((uint64_t) uConfiguration.burst_max, (uint64_t)(file->size + file->base - file->pos));
		
		/* Get space for writing. */
		record = dag_tx_get_stream_space(uDagFd, uConfiguration.stream, burst_length);
		if (!record) {
			if (errno == EAGAIN)
				continue;
			else
				dagutil_panic("Couldn't get tx space\n");
		}

		/* Copy bytes. */
		memcpy(record, file->pos, burst_length);
		
		/* Commit data. */
		record = dag_tx_stream_commit_bytes(uDagFd, uConfiguration.stream, burst_length);

		/* Advance offsets and counters. */
		uCounters.wbytes += burst_length;
		file->pos += burst_length;

		/* Keep file pointer inside boundaries. */
		if (file->pos >= file->base + file->size)
		{
			file->pos = file->base;
			uConfiguration.repeat--;
		}

		/* Should we repeat more times? */
		/* We were running dagflood with a repeat number option */
		if ((uConfiguration.repeat_mode == RUN_REPEAT) && (uConfiguration.repeat == 0))
		{
			uContinue = 0;
		}

#if defined(_WIN32)
		while (PeekMessage(&msg, NULL, 0, 0, PM_QS_POSTMESSAGE|PM_REMOVE) != 0)
		{
			DispatchMessage(&msg);
		}
#endif /*_WIN32 */

		/* Inter burst delay. */
		if (uConfiguration.usleep) {
			if (uConfiguration.accuracy) {
				if (uConfiguration.busywait)
					dagutil_sleep_until(wake_time);
				else
					dagutil_sleep_until_nobusy(wake_time);
			} else {
				if (uConfiguration.busywait)
					dagutil_microsleep(uConfiguration.usleep);
				else
					usleep(uConfiguration.usleep);
			}
		}

	}

	/* Wait for the stream to be almost empty.
	 * This way we are sure almost all the records are transmitted when we exit.
	 */
	flush_tx_buffer();
}

/*****************************************************************************/
static void
mmap_time_packets(file_t * file)
{
	uint32_t burst_length = 0;
	uint8_t *record = NULL;
	uint64_t entry_time, start_time = 0, send_time = 0, delta = 0;
	uint64_t last_time = 0, last_wlen = 0;
	uint16_t rlen;
	int init=0;
	uint64_t dagsleep;

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

	if (uConfiguration.nsleep)
		dagsleep = (((uint64_t)uConfiguration.nsleep << 32) / 1000 / 1000 / 1000);
	else if (uConfiguration.usleep)
		dagsleep = (((uint64_t)uConfiguration.usleep << 32) / 1000 / 1000);
	else
		dagsleep = 0;

	if (uConfiguration.time_mode == TIME_RATE) {
		dagsleep = (uint64_t) ( 0x100000000ll/uConfiguration.pkt_rate );
	}

	if (uConfiguration.resync) {
		entry_time = get_dag_time();
		if (entry_time != -1) {
			dagutil_verbose_level(2, "Entry Time 0x%016"PRIx64"\n", entry_time);
			
			start_time = entry_time + 0x100000000ull;
			dagutil_verbose_level(2, "Start Time 0x%016"PRIx64"\n", start_time);
		} else {
			dagutil_panic("Failed to Resync (-Y), requires DAG with Readable DUCK (-D).\n");
		}
	} else {
		start_time = ((dag_record_t*)(file->pos))->ts;
	}
	
	if (dagsleep)
		dagutil_verbose_level(2, "Dagsleep   0x%016"PRIx64"\n", dagsleep);

	if (sync_all_ports(start_time) < 0) {
		dagutil_error("Unable to synchronise ports\n");
		return;
	}

	/* Main loop */
	while (uContinue)
	{
		/* Examine next packet */
		rlen = ntohs(((dag_record_t*)(file->pos))->rlen);
		
		if(burst_length + rlen > uConfiguration.burst_max) {
			dag_tx_stream_commit_records(uDagFd, uConfiguration.stream, burst_length);
			burst_length = 0;
			record = NULL;
		}

		/* Get space for writing. */
		if (NULL == record) {
			if( (record = dag_tx_get_stream_space(uDagFd, uConfiguration.stream, uConfiguration.burst_max)) == NULL) {
				if (errno == EAGAIN) {
					continue;
				} else {
					dagutil_panic("Couldn't get tx space\n");}
			}
		}
		
		if (!init) {
			last_time = ((dag_record_t*)(file->pos))->ts;
			if(uConfiguration.resync) {
				send_time = start_time;
			} else {
				send_time = last_time;
			}
			init = 1;
		}

		if (((dag_record_t*)(file->pos))->ts < last_time)
			last_time = ((dag_record_t*)(file->pos))->ts;

		switch (uConfiguration.time_mode) {
		case TIME_DIRECT:
			if (dagsleep) {
				delta = dagsleep;
			} else {
				delta = ((dag_record_t*)(file->pos))->ts - last_time;
			}
			break;

		case TIME_SCALE:
			delta = (uint64_t) ( (((dag_record_t*)(file->pos))->ts - last_time) / uConfiguration.scale );
			break;
			
		case TIME_RATE:
			delta = dagsleep;
			break;

		case TIME_BW:
			switch(uErfs[0]) {
			case ERF_TYPE_ETH:
			case ERF_TYPE_COLOR_ETH:
			case ERF_TYPE_COLOR_HASH_ETH:
			case ERF_TYPE_DSM_COLOR_ETH:
				last_wlen += (8 + 12);
				break;
			case ERF_TYPE_HDLC_POS:
			case ERF_TYPE_COLOR_HDLC_POS:
			case ERF_TYPE_COLOR_HASH_POS:
			case ERF_TYPE_DSM_COLOR_HDLC_POS:
				last_wlen += 1;
				break;
			}
			delta = ((last_wlen << 35) / uConfiguration.bw);
			break;
		}

		send_time += delta;

		dagutil_verbose_level(3, "Send time %"PRIu64" 0x%"PRIx64"\n", send_time, send_time);

		/* create record to transmit, new ts plus remainder of record */
		((dag_record_t*)record)->ts = send_time;
		memcpy(record+8, file->pos+8, rlen-8);

		last_time = ((dag_record_t*)(file->pos))->ts;
		last_wlen = (uint64_t)ntohs(((dag_record_t*)(file->pos))->wlen);

		burst_length += rlen;
		record += rlen;

		/* Advance offsets and counters. */
		uCounters.wbytes += rlen;
		file->pos += rlen;

		/* Keep file pointer inside boundaries. */
		if (file->pos >= file->base + file->size)
		{
			file->pos = file->base;
			uConfiguration.repeat--;
			last_time = ((dag_record_t*)(file->pos))->ts - delta;
		}

		/* Should we repeat more times? */
		/* We were running dagflood with a repeat number option */
		if ((uConfiguration.repeat_mode == RUN_REPEAT) && (uConfiguration.repeat == 0))
		{
			uContinue = 0;
		}

#if defined(_WIN32)
		while (PeekMessage(&msg, NULL, 0, 0, PM_QS_POSTMESSAGE|PM_REMOVE) != 0)
		{
			DispatchMessage(&msg);
		}
#endif /*_WIN32 */

	}

	/* Commit last partial block (< burst_max) */
	dag_tx_stream_commit_records(uDagFd, uConfiguration.stream, burst_length);

	/* Wait for the stream to be almost empty.
	 * This way we are sure almost all the records are transmitted when we exit.
	 */
	flush_tx_buffer();
}

/*****************************************************************************/
static void
copy_bytes(const file_t * file)
{
	uint32_t byte_cnt;
	void *tx_buf[2];
	uint8_t flip = 0;
	unsigned long long wake_time;

	tx_buf[0] = tx_buf1;
	tx_buf[1] = tx_buf2;

	byte_cnt = fread(tx_buf[flip], sizeof(char), uConfiguration.burst_max, file->fp);
	if (byte_cnt < uConfiguration.burst_max)
	{
		if (!feof(file->fp))
			dagutil_panic("%s:%d file error\n", __FUNCTION__, __LINE__);
	}

	/* Main loop */
	while (uContinue)
	{
		if ( (uConfiguration.usleep) && (uConfiguration.accuracy) ) {
			dagutil_sleep_get_wake_time(&wake_time, uConfiguration.usleep);
		}

		/* Write to dag card (copy, so it's not zero-copy). */
		dag_tx_stream_copy_bytes (uDagFd, uConfiguration.stream, (void*)tx_buf[flip], byte_cnt);
		flip = !flip;

		/* Advance counters. */
		uCounters.wbytes += byte_cnt;
		
		if (feof(file->fp))
		{
			uConfiguration.repeat--;
			if (uConfiguration.repeat)
				if (fseek(file->fp,0L,SEEK_SET))
					dagutil_panic("%s:%d file error\n", __FUNCTION__, __LINE__);
		}
					
       	/* Should we repeat more times? */
		/* We were running dagflood with a repeat number option */


		if ((uConfiguration.repeat_mode == RUN_REPEAT) && (uConfiguration.repeat == 0))
		{
			uContinue = 0;
			fclose(file->fp);
		}
		else
		{
			byte_cnt = fread(tx_buf[flip], sizeof(char), uConfiguration.burst_max, file->fp);
			if (byte_cnt < uConfiguration.burst_max)
			{
				if (!feof(file->fp))
					dagutil_panic("%s:%d file error\n", __FUNCTION__, __LINE__);
			}
		}

		/* Inter burst delay. */
		if (uConfiguration.usleep) {
			if (uConfiguration.accuracy) {
				if (uConfiguration.busywait)
					dagutil_sleep_until(wake_time);
				else
					dagutil_sleep_until_nobusy(wake_time);
			} else {
				if (uConfiguration.busywait)
					dagutil_microsleep(uConfiguration.usleep);
				else
					usleep(uConfiguration.usleep);
			}
		}

	}

	/* Wait for the stream to be almost empty */
	/* This way we are sure almost all the records are transmitted when we exit. */
	flush_tx_buffer();
}
/*****************************************************************************/
static void
commit_bytes(const file_t * file)
{
	uint32_t byte_cnt;
	void * record;
	unsigned long long wake_time;

#if defined(_WIN32)
	MSG msg;
#endif /* _WIN32 */
	
	/* Main loop */
	while (uContinue)
	{
		if ( (uConfiguration.usleep) && (uConfiguration.accuracy) ) {
			dagutil_sleep_get_wake_time(&wake_time, uConfiguration.usleep);
		}

		/* Get space for writing. */
		record = dag_tx_get_stream_space(uDagFd, uConfiguration.stream, uConfiguration.burst_max);
		if (!record) {
			if (errno == EAGAIN)
				continue;
			else
				dagutil_panic("Couldn't get tx space\n");
		}
		
		byte_cnt = fread(record , sizeof(char), uConfiguration.burst_max, file->fp);
		if (byte_cnt < uConfiguration.burst_max)
		{
			if (!feof(file->fp))
				dagutil_panic("%s:%d file error\n", __FUNCTION__, __LINE__);
		}

		/* Commit data. */
		record = dag_tx_stream_commit_bytes(uDagFd, uConfiguration.stream, byte_cnt);

		/* Advance counters. */
		uCounters.wbytes += byte_cnt;

		if (feof(file->fp))
		{
			uConfiguration.repeat--;
			if (uConfiguration.repeat)
			{
				if (fseeko(file->fp,0,SEEK_SET))
				{
					dagutil_panic("%s:%d file error\n", __FUNCTION__, __LINE__);
				}
			}
		}
		
        /* Should we repeat more times? */
		/* We were running dagflood with a repeat number option */
		if ((uConfiguration.repeat_mode == RUN_REPEAT) && (uConfiguration.repeat == 0))
		{
			uContinue = 0;
			fclose(file->fp);
		}

#if defined(_WIN32)
		while (PeekMessage(&msg, NULL, 0, 0, PM_QS_POSTMESSAGE|PM_REMOVE) != 0)
		{
			DispatchMessage(&msg);
		}
#endif /*_WIN32 */

		/* Inter burst delay. */
		if (uConfiguration.usleep) {
			if (uConfiguration.accuracy) {
				if (uConfiguration.busywait)
					dagutil_sleep_until(wake_time);
				else
					dagutil_sleep_until_nobusy(wake_time);
			} else {
				if (uConfiguration.busywait)
					dagutil_microsleep(uConfiguration.usleep);
				else
					usleep(uConfiguration.usleep);
			}
		}

	}

	/* Wait for the stream to be almost empty.
	 * This way we are sure almost all the records are transmitted when we exit.
	 */
	flush_tx_buffer();
}

/*****************************************************************************/
static void
time_packets(const file_t * file)
{
	uint32_t byte_cnt;
	uint8_t * record = NULL;
	uint64_t entry_time, start_time = 0, send_time = 0, delta = 0;
	uint64_t last_time = 0, last_wlen = 0;
	uint16_t rlen;
	int init=0;
	uint64_t dagsleep;
	uint32_t burst_length = 0;
	int rewound = 0;

#if defined(_WIN32)
	MSG msg;
#endif /* _WIN32 */
	
	if (uConfiguration.nsleep)
		dagsleep = (((uint64_t)uConfiguration.nsleep << 32) / 1000 / 1000 / 1000);
	else if (uConfiguration.usleep)
		dagsleep = (((uint64_t)uConfiguration.usleep << 32) / 1000 / 1000);
	else
		dagsleep = 0;

	if (uConfiguration.time_mode == TIME_RATE) {
		dagsleep = (uint64_t) ( 0x100000000ll/uConfiguration.pkt_rate );
	}

	if(uConfiguration.resync) {
		entry_time = get_dag_time();
		if (entry_time != -1) {
			dagutil_verbose_level(2, "Entry Time 0x%016"PRIx64"\n", entry_time);
			
			start_time = entry_time + 0x100000000ull;
			dagutil_verbose_level(2, "Start Time 0x%016"PRIx64"\n", start_time);
		} else {
			dagutil_panic("Failed to Resync (-Y), requires DAG with Readable DUCK (-D).\n");
		}
	} else {
		/* Examine first packet */
		record = malloc(dag_record_size);
		if(!record)
			dagutil_panic("time_packets:%d %s\n", __LINE__, strerror(errno));

		byte_cnt = fread(record, sizeof(char), dag_record_size, file->fp);
		if (byte_cnt < dag_record_size)
		{
			if (!feof(file->fp))
				dagutil_panic("%s:%d file error\n", __FUNCTION__, __LINE__);
		}

		/* get start time */
		start_time = ((dag_record_t*)record)->ts;

		free(record);
		record = NULL;

		/* rewind */
		if (fseeko(file->fp,0,SEEK_SET))
			dagutil_panic("%s:%d file seek error %s\n", __FUNCTION__, __LINE__, strerror(errno));
	}

	if (dagsleep)
		dagutil_verbose_level(2, "Dagsleep   0x%016"PRIx64"\n", dagsleep);

	if (sync_all_ports(start_time) < 0) {
		dagutil_error("Unable to synchronise ports\n");
		return;
	}

	/* Main loop */
	while (uContinue)
	{
		if(burst_length + dag_record_size > uConfiguration.burst_max) {
			dag_tx_stream_commit_records(uDagFd, uConfiguration.stream, burst_length);
			burst_length = 0;
			record = NULL;
		}

		/* Get space for writing. */
		if (NULL == record) {
			if( (record = dag_tx_get_stream_space(uDagFd, uConfiguration.stream, uConfiguration.burst_max)) == NULL) {
				if (errno == EAGAIN) {
					continue;
				} else {
					dagutil_panic("Couldn't get tx space\n");
				}
			}
		}

		/* Examine next packet */
		byte_cnt = fread(record, sizeof(char), dag_record_size, file->fp);
		if (byte_cnt < dag_record_size)
		{
			if (!feof(file->fp))
				dagutil_panic("%s:%d file error\n", __FUNCTION__, __LINE__);
		}

		rlen = ntohs(((dag_record_t*)record)->rlen);

		if(burst_length + rlen > uConfiguration.burst_max) {
			dag_tx_stream_commit_records(uDagFd, uConfiguration.stream, burst_length);
			burst_length = 0;
			record = NULL;
			fseeko(file->fp, -dag_record_size, SEEK_CUR);
			continue;
		}

		/* read remainder of packet */
		byte_cnt = fread(record + dag_record_size, sizeof(char), rlen - dag_record_size, file->fp);
		if (byte_cnt < (rlen - dag_record_size))
		{
			if (!feof(file->fp))
				dagutil_panic("%s:%d file error\n", __FUNCTION__, __LINE__);
		}		

		if (!init) {
			last_time = ((dag_record_t*)record)->ts;
			if(uConfiguration.resync) {
				send_time = start_time;
			} else {
				send_time = last_time;
			}
			init = 1;
		}

		if (rewound) {
			last_time = ((dag_record_t*)record)->ts - delta;
			rewound = 0;
		}

		if (((dag_record_t*)record)->ts < last_time)
			last_time = ((dag_record_t*)record)->ts;

		switch (uConfiguration.time_mode) {
		case TIME_DIRECT:
			if (dagsleep) {
				delta = dagsleep;
			} else {
				delta = ((dag_record_t*)record)->ts - last_time;
			}
			break;

		case TIME_SCALE:
			delta = (uint64_t) ( (((dag_record_t*)record)->ts - last_time) / uConfiguration.scale );
			break;
			
		case TIME_RATE:
			delta = dagsleep;
			break;

		case TIME_BW:
			switch(uErfs[0]) {
			case ERF_TYPE_ETH:
			case ERF_TYPE_COLOR_ETH:
			case ERF_TYPE_COLOR_HASH_ETH:
			case ERF_TYPE_DSM_COLOR_ETH:
				last_wlen += (8 + 12);
				break;
			case ERF_TYPE_HDLC_POS:
			case ERF_TYPE_COLOR_HDLC_POS:
			case ERF_TYPE_COLOR_HASH_POS:
			case ERF_TYPE_DSM_COLOR_HDLC_POS:
				last_wlen += 1;
				break;
			}
			delta = ((last_wlen << 35) / uConfiguration.bw);
			break;
		}

		send_time += delta;
		dagutil_verbose_level(3, "Send time %"PRIu64" 0x%"PRIx64"\n", send_time, send_time);

		last_time = (((dag_record_t*)record)->ts);
		last_wlen = (uint64_t)ntohs(((dag_record_t*)record)->wlen);

		/* create record to transmit, new ts plus remainder of record */
		(((dag_record_t*)record)->ts) = send_time;

		burst_length += rlen;
		record += rlen;

		/* Advance counters. */
		uCounters.wbytes += rlen;

		if (feof(file->fp))
		{
			uConfiguration.repeat--;
			if (uConfiguration.repeat)
			{
				if (fseeko(file->fp,0,SEEK_SET))
				{
					dagutil_panic("%s:%d file error\n", __FUNCTION__, __LINE__);
				}
				rewound = 1;
			}
		}
		
		/* Should we repeat more times? */
		/* We were running dagflood with a repeat number option */
		if ((uConfiguration.repeat_mode == RUN_REPEAT) && (uConfiguration.repeat == 0))
		{
			uContinue = 0;
			fclose(file->fp);
		}

#if defined(_WIN32)
		while (PeekMessage(&msg, NULL, 0, 0, PM_QS_POSTMESSAGE|PM_REMOVE) != 0)
		{
			DispatchMessage(&msg);
		}
#endif /*_WIN32 */

	}

	/* Commit last partial block (< burst_max) */
	dag_tx_stream_commit_records(uDagFd, uConfiguration.stream, burst_length);

	/* Wait for the stream to be almost empty.
	 * This way we are sure almost all the records are transmitted when we exit.
	 */
	flush_tx_buffer();
}

/*****************************************************************************/
/* Signal processing */
void
sighandler (int signal)
{

	if (uContinue) {
		/* in transmit loop, exit that */
		uContinue = 0;
	} else if (uConfiguration.flush_tx_buffer) {
		/* in flush_tx_buffer already, exit that */
		uConfiguration.flush_tx_buffer = 0;
	} else {
		/* just die already */
		exit(EXIT_FAILURE);
	}

}

/*****************************************************************************/

/* Time signal handling. */
#if defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))

void
time_handler(int signal)

#elif defined(_WIN32)

VOID CALLBACK
time_handler(LPVOID lpArgToCompletionRoutine, DWORD dwTimerLowValue, DWORD dwTimerHighValue)

#else
#error Compiling on an unsupported platform - please contact <support@endace.com> for assistance.
#endif /* Platform-specific code. */

{
	uint64_t temp1;
	uint32_t per1, per2;
	struct timeval tv;

	if (uConfiguration.start_time) {
		if (terf_trigger_pending()) {
			if (dagutil_get_verbosity() > 0) {			
				gettimeofday(&tv, NULL);
				fprintf (stderr, "Waiting for start time, %d seconds remaining    \r", uConfiguration.start_time - (int)tv.tv_sec);
			}
			uCounters.seconds = -1;			
		} else {
			if (dagutil_get_verbosity() > 0) {
				fprintf (stderr, "Starting now                                    \r");			
			}
			uConfiguration.start_time = 0;
			uCounters.seconds = 0;
		}
	} else {

		/* Increment number of seconds the application has been running. */
		uCounters.seconds++;

		if (uContinue) {

			/* Report some statistics. */
			if (dagutil_get_verbosity() > 0)
			{
				fprintf (stderr, "Rate: %4.2ld Mbps (%4.2ld MiB/s) ",
					 (long int) ((uCounters.wbytes - uCounters.old_wbytes) * 8 / 1000000),
					 (long int) ((uCounters.wbytes - uCounters.old_wbytes) / ONE_MEBI));
				switch(uConfiguration.repeat_mode) {
				case RUN_TIMELIMIT:
					if (uConfiguration.timelimit) {
						temp1 = uCounters.seconds*10000/uConfiguration.timelimit;
						per1 = (uint32_t)temp1/100;
						per2 = (uint32_t)temp1%100;
						fprintf (stderr, "Run: %d sec Remaining: %d sec Completion: %02d.%02d%%\r",
							 uCounters.seconds,
							 uConfiguration.timelimit-uCounters.seconds,
							 per1, per2);
					} else {
						fprintf (stderr, "Run: %d sec no timeout set\r",
							 uCounters.seconds);	
					}
					break;
				case RUN_REPEAT:
					temp1 = uCounters.wbytes*10000/uCounters.bytes_to_send;
					per1 = (uint32_t)temp1/100;
					per2 = (uint32_t)temp1%100;
					fprintf (stderr, "Repeat: %d/%d Completion: %02d.%02d%%\r",
						 uConfiguration.total_repeats-uConfiguration.repeat+1,
						 uConfiguration.total_repeats,
						 per1, per2);
					break;
				default:
					dagutil_panic("unknown repeat mode %d", uConfiguration.repeat_mode);
				}

				/* Update counters. */
				uCounters.old_wbytes = uCounters.wbytes;
			}
		} else {
			/* run time has ended, flushing tx buffer */
			if ( (uConfiguration.flush_tx_buffer) && (dagutil_get_verbosity() > 0) )
			{
				fprintf( stderr, "Tx buffer level %.1f%%    \r",
					 (double)dag_get_stream_buffer_level(uDagFd, uConfiguration.stream) * 100.0 / (double)dag_get_stream_buffer_size(uDagFd, uConfiguration.stream) );
			}
		}

		/* Have we reached the time limit? */
		if (uCounters.seconds == uConfiguration.timelimit)
		{
			/* End program. */
			uContinue = 0;
		}

	}

#if defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))
	/* In Windows the signal is already set up to elapse again after a second.
	 * Reprogram alarm.
	 */
	alarm(1);
#endif

}

/*****************************************************************************/
void
init_options(void)
{
	memset (&uConfiguration, 0, sizeof(uConfiguration));

	uConfiguration.burst_max = BURST_MAX;
	uConfiguration.repeat_mode  = RUN_REPEAT;
	uConfiguration.run_mode  = MODE_COMMIT;
	uConfiguration.flush_tx_buffer = 1;
	uConfiguration.repeat = 1;
	uConfiguration.total_repeats = 1;
	uConfiguration.accuracy = 0;
	uConfiguration.busywait = 0;
	uConfiguration.resync = 0;
	uConfiguration.settimedevice = 0;
	uConfiguration.odirect = 0;
	uConfiguration.replay = 0;
	uConfiguration.time_mode = TIME_DIRECT;
	uConfiguration.scale = 1.0;
	uConfiguration.pkt_rate = 0;
	uConfiguration.bw = 0;
	uConfiguration.start_time = 0;
	strncpy(uConfiguration.device,dagname,DAGNAME_BUFSIZE);
	strncpy(uConfiguration.timedevice,timename,DAGNAME_BUFSIZE);
}



/*****************************************************************************/
#if defined(_WIN32)
void
remove_mapping(file_t * file)
{
	UnmapViewOfFile(file->base);

	CloseHandle(file->mapping);
}
#endif /* _WIN32 */ 
/*****************************************************************************/
void
report_statistics(void)
{
	double total_time;

	total_time = (uCounters.end.tv_sec - uCounters.start.tv_sec) + (((uCounters.end.tv_usec) - uCounters.start.tv_usec) % 1000000) / 1000000.0;

	printf ("------------------------------------------------------------------------------\n");
	
#if defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))

	printf ("total bytes sent to dag card: %lld (%.2f MiB)\n", (long long int) uCounters.wbytes, ((double) uCounters.wbytes) / ONE_MEBI);
	
#elif defined(_WIN32)

	printf ("total bytes sent to dag card: %10I64u (%.2f MiB)\n", uCounters.wbytes, ((double) uCounters.wbytes) / ONE_MEBI);

#else
#error Compiling on an unsupported platform - please contact <support@endace.com> for assistance.
#endif /* Platform-specific code. */

	printf ("approx. average speed: %4.2f Mbps (%4.2f MiB/s)\n", (uCounters.wbytes * 8 / total_time) / 1000000, (uCounters.wbytes / total_time) / 1000000);
}

/*****************************************************************************/
int get_erfs_from_input_file(const file_t *file, uint8_t *in_erfs)
{
    int cur_offset = 0;
    const dag_record_t *cur_rec = NULL;
    char read_buf[dag_record_size];
    int read_len = 0;
    /* TODO what if there is more than one type in file? */

    do
    {
        if (!mmap_failed)
        {
            if ( cur_offset >= file->size)
            {
                dagutil_error("No valid (non-pad) ERF records found in file.\n");
                return -1;
            }
            cur_rec = (dag_record_t*)((char*)file->base+cur_offset);
        }
        else
        {
            if (fseek(file->fp, cur_offset, SEEK_SET))
            {
	        if (errno == EINVAL) {
		     dagutil_error("No valid (non-pad) ERF records found in file.\n");
		} else {
		     dagutil_error("Error in file seek in %s:%u %s\n",__FILE__,__LINE__, strerror(errno));
		}
                fclose(file->fp);
                return -1;
            }
            read_len = fread(read_buf, sizeof(char), dag_record_size, file->fp);
            if( dag_record_size != read_len )
            {
		if (feof(file->fp)) {
		     dagutil_error("No valid (non-pad) ERF records found in file.\n");
		} else {
		     dagutil_error("Error reading file in %s:%u %s\n",__FILE__,__LINE__, strerror(errno));
		}
                fclose(file->fp);
                return -1;
            }
            cur_rec = (dag_record_t*) read_buf;
        }
        cur_offset += ntohs(cur_rec->rlen);
    } while (cur_rec && ( cur_rec->type == TYPE_PAD) );

    if( cur_rec ) {
        in_erfs[0] = cur_rec->type;
	dagutil_verbose("File contains ERF type %d (%s)\n", cur_rec->type, dagerf_type_to_string(cur_rec->type, 0));
    }
    in_erfs[1] = 0;

    if (file->fp) {
	    if (fseeko(file->fp,0,SEEK_SET))
		 dagutil_panic("%s:%d file seek error %s\n", __FUNCTION__, __LINE__, strerror(errno));
    }
    return 0;
}

/*****************************************************************************/
int
dagflood_main(int argc, char **argv)
{
	file_t file; /* file queue */
	FILE* errorfile = NULL;
	ClArgPtr clarg = NULL;
	int argindex;
	int code;
	int result;
	int unused;
	char	*more;

#if defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))
	struct  sigaction sigact; /* signal catching */
	
#elif defined(_WIN32)

	HANDLE hTimer = NULL;
	LARGE_INTEGER liDueTime;
	/* set the timer to expire every second */
	liDueTime.QuadPart = -10000000;
	
#else
#error Compiling on an unsupported platform - please contact <support@endace.com> for assistance.
#endif /* Platform-specific code. */

	dagutil_set_progname("dagflood");

	/* Set up default DAG device. */
	if (-1 == dag_parse_name(dagname_buf, dagname, DAGNAME_BUFSIZE, &uConfiguration.stream))
	{
		dagutil_panic("dag_parse_name(%s): %s\n", dagname_buf, strerror(errno));
	}
	if (-1 == dag_parse_name(timename_buf, timename, DAGNAME_BUFSIZE, &unused))
	{
		dagutil_panic("dag_parse_name(%s): %s\n", timename_buf, strerror(errno));
	}
	init_options();
	
	if( strchr(dagname_buf,':')  == NULL ) 
			uConfiguration.stream = 1;


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

	dagclarg_add(clarg, "Make inter-burst delay (-u) include the data reading/copying time. Recommended.", "--accuracy", 'a', CLA_ACCURACY);
	dagclarg_add(clarg, "Increases accuracy of inter-burst delay (-u) timing, but uses 100% of one CPU core.", "--busywait", 'b', CLA_BUSYWAIT);
	dagclarg_add_uint(clarg, "Transmit file <count> times before exiting.", "--count", 'c', "count", &count, CLA_COUNT);
	dagclarg_add_string(clarg, "DAG device to use.  Default: dag0.", "--device", 'd', "device", dagname_buf, DAGNAME_BUFSIZE, CLA_DEVICE);
	dagclarg_add_string(clarg, "File containing ERF records to send.", "--fname", 'f', "filename", fname_buf, BUFSIZE, CLA_FNAME);
	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, "Use O_DIRECT to improve file read performance on some systems.", "--odirect", 'j', CLA_ODIRECT);
	dagclarg_add_string(clarg, "Set maximum data block to read/transmit at once. Supports k, m, and g units. Default: 1M.", "--burst-max", 'l', "bytes[kmg]", bmax_buf, BUFSIZE, CLA_BMAX);
	dagclarg_add_uint(clarg, "Set Replay (-R) inter-packet time stamp delta (nanoseconds). Inverse of -p.","--nsdelay", 'n', "nanoseconds", &uConfiguration.nsleep, CLA_NSDELAY);
	dagclarg_add_string(clarg, "Set Replay (-R) packet rate in packets per second. Supports k,m,and g units.", "--rate", 'p', "pps[kmg]", rate_buf, BUFSIZE, CLA_RATE);
	dagclarg_add_string(clarg, "API mode. 1 = commit bytes, 2 = copy bytes, 3 = commit packets.","--mode", 'r', "api-mode", mode_buf, BUFSIZE, CLA_MODE);
	dagclarg_add(clarg, "Replay file using packet timestamps in hardware (time_mode=relative).", "--replay", 'R', CLA_REPLAY);
	dagclarg_add_string(clarg, "Scale Replay (-R) transmit rate. Supports decimal points and optional % (1.0x = 100%). Default: 100%.", "--scale", 's', "times or percent", scale_buf, BUFSIZE, CLA_SCALE);
	dagclarg_add_uint(clarg, "Start transmitting at <time> (seconds from UNIX Epoch).","--start-time", 'S', "time", &uConfiguration.start_time, CLA_START);
	dagclarg_add_uint(clarg, "Stop transmitting after <n> seconds.", "--terminate", 't', "seconds", &tlimit, CLA_TERMN);
	dagclarg_add_uint(clarg, "Inter-burst delay (microseconds).","--delay", 'u', "microseconds", &uConfiguration.usleep, CLA_DELAY);
	dagclarg_add(clarg, "Increase verbosity.", "--verbose", 'v', CLA_VERBOSE);
	dagclarg_add(clarg, "Display version information.", "--version", 'V', CLA_VERSION);
	dagclarg_add_string(clarg, "Set Replay (-R) transmit bandwidth in bits per second. Supports k, m, and g units.", "--bandwidth", 'w', "bps[kmg]", bw_buf, BUFSIZE, CLA_BW);
	dagclarg_add(clarg, "Do not flush transmit buffer when exiting. (Default: flush buffer)", "--no-flush", 'x', CLA_NFLUSH);

	/* suppressed options work but are not displayed in help */
	dagclarg_add_string(clarg, "DAG device to use for time reference (in -r3 -T mode only).  Default: use DAG device (-d).", "--timedevice", 'D', "timedevice", timename_buf, DAGNAME_BUFSIZE, CLA_TIMEDEVICE);
	dagclarg_suppress_display(clarg, CLA_TIMEDEVICE);
	dagclarg_add(clarg, "In rt commit mode (-r3) set first packet ts to current time.", "--resync", 'Y', CLA_RESYNC);
	dagclarg_suppress_display(clarg, CLA_RESYNC);

	/* 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(dagname_buf, uConfiguration.device, DAGNAME_BUFSIZE, &uConfiguration.stream))
			{
				dagutil_panic("dag_parse_name(%s): %s\n", dagname_buf, strerror(errno));
			}
			if( strchr(dagname_buf,':')  == NULL ) 
				uConfiguration.stream = 1;
			dagutil_verbose_level(2, "device=%s:%d\n", uConfiguration.device, uConfiguration.stream);
			break;

		case CLA_TIMEDEVICE:
			if (-1 == dag_parse_name(timename_buf, uConfiguration.timedevice, DAGNAME_BUFSIZE, &unused))
			{
				dagutil_panic("dag_parse_name(%s): %s\n", timename_buf, strerror(errno));
			}
			uConfiguration.settimedevice = 1;	
			dagutil_verbose_level(2, "timedevice=%s\n", uConfiguration.timedevice);
			break;

		case CLA_FNAME:
			uConfiguration.file = fname_buf;
			break;

		case CLA_COUNT:
			if (count > 0)
                        {
                                uConfiguration.repeat = count;
                                uConfiguration.total_repeats = count;
                                uConfiguration.repeat_mode  = RUN_REPEAT;
                        }
                        else
                        {
                                dagutil_error("transmit file <count> times is invalid\n");
                                return EXIT_FAILURE;
                        }
                        break;
			
		case CLA_TERMN:
			uConfiguration.timelimit = tlimit;
			uConfiguration.repeat_mode  = RUN_TIMELIMIT;
			uConfiguration.flush_tx_buffer = 0;
			break;

		case CLA_BMAX:
			uConfiguration.burst_max = strtol(bmax_buf, &more, 0);
			switch(*more) {
			case '\0':
				break;
			case 'K':
			case 'k':
				uConfiguration.burst_max *= KB;
				break;
			case 'M':
			case 'm':
				uConfiguration.burst_max *= MB;
				break;
			case 'G':
			case 'g':
				uConfiguration.burst_max *= GB;
				break;
			default:
				dagutil_panic("unrecognized character '%c' specification after -l/--burst-max, (kKmMgG supported)\n", *more);
				break;			
			}
			printf("Burst-max set to %d bytes\n", uConfiguration.burst_max);
			break;
			
		case CLA_HELP:
			print_usage(clarg);
			return EXIT_SUCCESS;
			break;

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

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

		case CLA_MODE:
			uConfiguration.run_mode = strtoul(mode_buf, NULL, 0);
			break;

		case CLA_DELAY:
			/* value handled by clarg */
			break;

		case CLA_NSDELAY:
			/* value handled by clarg */
			uConfiguration.replay = 1;
			/* force mode to MODE_PERPKT */
			uConfiguration.run_mode = MODE_PERPKT;
			break;
		
		case CLA_NFLUSH:
			uConfiguration.flush_tx_buffer = 0;
			break;

		case CLA_ACCURACY:
			uConfiguration.accuracy = 1;
			break;

		case CLA_BUSYWAIT:
			uConfiguration.busywait = 1;
			break;

		case CLA_RESYNC:
			uConfiguration.resync = 1;
			/* force mode to MODE_PERPKT */
			uConfiguration.run_mode = MODE_PERPKT;
			break;

		case CLA_ODIRECT:
			uConfiguration.odirect = 1;
			break;

		case CLA_REPLAY:
			uConfiguration.replay = 1;
			/* force mode to MODE_PERPKT */
			uConfiguration.run_mode = MODE_PERPKT;
			break;

		case CLA_SCALE:
			uConfiguration.scale = strtof(scale_buf, &more);
			switch(*more) {
			case '\0':
				break;
			case 'x':
				break;
			case '%':
				uConfiguration.scale /= 100;
				break;
			default:
				dagutil_panic("unrecognized character '%c' specification after -s/--scale, (x or %% supported)\n", *more);
				break;			
			}
			printf("Replay scale set to %.3f%%\n", uConfiguration.scale * 100);
			uConfiguration.time_mode = TIME_SCALE;

			/* force replay on */
			uConfiguration.replay = 1;

			/* force mode to MODE_PERPKT */
			uConfiguration.run_mode = MODE_PERPKT;			

			break;
			
		case CLA_RATE:
			uConfiguration.pkt_rate = strtoul(rate_buf, &more, 0);
			switch(*more) {
			case '\0':
				break;
			case 'K':
			case 'k':
				uConfiguration.pkt_rate *= 1000;
				break;
			case 'M':
			case 'm':
				uConfiguration.pkt_rate *= 1000000;
				break;
			case 'G':
			case 'g':
				uConfiguration.pkt_rate *= 1000000000;
				break;
			default:
				dagutil_panic("unrecognized character '%c' specification after -p/--rate, (kKmMgG supported)\n", *more);
				break;			
			}
			printf("Replay packet rate set to %u pps\n", uConfiguration.pkt_rate);

			uConfiguration.time_mode = TIME_RATE;

			/* force replay on */
			uConfiguration.replay = 1;

			/* force mode to MODE_PERPKT */
			uConfiguration.run_mode = MODE_PERPKT;			

			break;
		      
		case CLA_BW:
			uConfiguration.bw = (uint64_t)strtoull(bw_buf, &more, 0);
			switch(*more) {
			case '\0':
				break;
			case 'K':
			case 'k':
				uConfiguration.bw *= 1000;
				break;
			case 'M':
			case 'm':
				uConfiguration.bw *= 1000000;
				break;
			case 'G':
			case 'g':
				uConfiguration.bw *= 1000000000;
				break;
			default:
				dagutil_panic("unrecognized character '%c' specification after -w/--bandwidth, (kKmMgG supported)\n", *more);
				break;			
			}
			printf("Replay bandwidth set to %"PRIu64" bps\n", uConfiguration.bw);

			uConfiguration.time_mode = TIME_BW;

			/* force replay on */
			uConfiguration.replay = 1;

			/* force mode to MODE_PERPKT */
			uConfiguration.run_mode = MODE_PERPKT;			

			break;

		case CLA_START:
			/* value handled by clarg */
			break;

		default:
			/* Unknown option. */
				dagutil_error("unknown option %s\n", argv[argindex]); 
				print_usage(clarg);
				return EXIT_FAILURE;
		}

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

	if (-1 == result)
	{
		if (argindex < argc)
		{
			dagutil_error("while processing option %s\n", argv[argindex]);
		}
		dagclarg_display_usage(clarg, stderr);
		return EXIT_FAILURE;
	}
	
	/* Check for data file */
	if (uConfiguration.file != NULL)
	{
		init_file(uConfiguration.file, &file);
	}
	else
	{
		print_usage(clarg);
		dagutil_panic("error: must specify one transmit file\n");
	}

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

	/* validate the input configuration parameters before proceeding */
	if ( -1 == validate_input_configuration(/*uConfiguration,*/&file) )
	{
		dagutil_panic("error: validation failed\n");
	}

 	/* Init dag session and device */
	initialize_dag();

	/* Catch signals */
	dagutil_set_signal_handler(sighandler);

	/* Handle alarm signals in another function */
#if defined(__FreeBSD__) || defined(__linux__) || (defined(__SVR4) && defined(__sun)) || (defined(__APPLE__) && defined(__ppc__))

	sigact.sa_handler = time_handler;
	sigemptyset(&sigact.sa_mask);
	sigact.sa_flags = SA_RESTART;
	if (sigaction(SIGALRM, &sigact, NULL) < 0)
		dagutil_panic("sigaction SIGALRM: %s\n", strerror(errno));
	
	/* Set alarm signal every second */
	alarm(1);
	
#elif defined(_WIN32)

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

#else
#error Compiling on an unsupported platform - please contact <support@endace.com> for assistance.
#endif /* Platform-specific code. */

	/* Record start time */
	gettimeofday(&uCounters.start, 0);

	/* Set stream erf type */
        if(get_erfs_from_input_file(&file, uErfs)){
		dagutil_panic("Failed to determine ERF types in file: %s\n",
			      uConfiguration.file);
	}
	dag_set_stream_erf_types(uDagFd, uConfiguration.stream, uErfs);

	/* Run_mode one and two use different API interfaces.
	 * This is only for educational and testing purposes.
	 * You really only need one of these two interfaces.

	 * Run_mode 3 (MODE_PERPKT) is for timed release where
	 * we modify packet timestamps and transmit record
	 * aligned.
	 */
	switch (uConfiguration.run_mode)
	{
		case MODE_COMMIT:
			if (!mmap_failed)
				mmap_commit_bytes(&file);
			else
				commit_bytes(&file);
			break;
			
		case MODE_COPY:
			if (!mmap_failed)
				mmap_copy_bytes(&file);
			else
				copy_bytes(&file);
			break;

		case MODE_PERPKT:
			if (!mmap_failed)
				mmap_time_packets(&file);
			else
				time_packets(&file);
			break;
		
		default:
			dagutil_panic("API mode %d not supported\n", uConfiguration.run_mode);
			break;
	}

	/* Record end time */
	gettimeofday(&uCounters.end, 0);

	/* Report some statistics */
	report_statistics();

#if defined(_WIN32)
	/* Clean up the file structures used if running on Windows */
	if (!mmap_failed)
		remove_mapping(&file);
#endif /* WIN32 */

	/* Close dag session and device */
	finalize_dag();

	return EXIT_SUCCESS;
}


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