//
//  QTCP.C version 1.0.3
//
// This program tests the quality of a network connection in terms of 
// variation in latency (jitter). It is based on TTCP, a public domain 
// program, written for BSD. The version of TTCP upon which this was
// based has been contributed to by:
//
//      T.C. Slattery, USNA (18 Dec 84)
//      Mike Muuss and T. Slattery (16 Oct 85)
//      Silicon Graphics, Inc. (1989)
//
// QTCP is written by Yoram Bernet (yoramb@microsoft.com)
//      further development work by John Holmes (jsholmes@mit.edu)
// 
// QTCP user level code may be used to provide rough jitter measurements,
// which indicate both operating system and network jitter. However, QTCP
// is intended to be used in conjunction with kernel timestamping for precise
// jitter measurements. The kernel component timestmp.sys should be installed
// when running on Win2000 (beta-3 or later).
//
// timestmp.sys is written by Shreedhar Madhavapeddi (shreem@microsoft.com)
//
//
// Distribution Status -
//      Public Domain.  Distribution Unlimited.
//

// Version History -
//   0.8:
//		- adaptation of TTCP by Yoram Bernet -- core functionality
//   0.9: (6/15/99)
//		- first version by John Holmes -- bug fixes and new features
//      - fixed all compile warnings
//      - added -v option to set up RSVP connection without sending data
//      - added -y option to skip confirmation of continues
//      - fixed line length error in log files
//      - fixed service type string to display properly
//      - added best effort and no service service types (BE & NS)
//      - added version string print upon execution
//   0.9.1: (6/17/99)
//      - check for hardware clock reset using correlation coefficient
//      - fixed incorrect clock skew in .sta file
//      - fixed -v option to keep socket open until user carriage returns
//      - added local statistics to clock skew computation for better estimate
//      - added -k option to prevent using local statistics for clock skew
//      - fixed maximum latency computation
//   0.9.2: (6/23/99)
//  	- fixed peak rate in flowspec so no shaping happens in CL servicetype
//      - added -e option to force shaping
//      - fixed error in allocating size of log array with bufsize <= 1500 bytes
//      - fixed not exiting on receiver
//      - fixed access violation if no filename specified on receiver
//      - changed dummy log entries to be off by default
//      - added -F option to convert raw file to log file
//   0.9.3: (6/29/99)
//      - improved low transmission speed at high packet/second rates by changing
//        default # async buffers from 3 to 32
//      - fixed user mode timestamps to use NtQueryPerformanceCounter()
//      - added -u option to use usermode timestamps in log generation
//   0.9.4: (7/8/99)
//      - cleaned up source (chopped up main into a bunch of functions to improve readability)
//      - fixed default buffer size to be 1472 bytes, so whole packet is 1500 bytes.
//      - rewrote i/o code to use callbacks for asynch i/o in order to improve throughput
//      - doing the right thing if not getting kernel-mode timestamps
//      - added ability to run for a specified amount of time with -n##s paramater
//      - added dynamic resizing of log array on receiver to prevent access violations
//        with mismatched parameters
//      - fixed devious bug in the GrowLogArray routine
//      - fixed total time reported for long runs (use UINT64 instead of DWORD)
//      - fixed problem with -F option specified on empty but extant file
//      - added RSVPMonitor Thread to watch for RSVP-err messages on receiver and
//        early abort by sender
//      - removed -a option as it is now obsolete
//      - revised usage screen to make more clear what pertains to sender and what
//        pertains to receiver
//      - fixed crash if receiver terminates before transmitter finishes
//   0.9.5: (7/15/99)
//      - re-added error checking on WriteFileEx and ReadFileEx routines
//   0.9.6: (7/20/99)
//      - changed default filler data in buffer so that it is less compressible to 
//        better account for links that compress data before sending
//      - added -i option to use more compressible data
//   0.9.7: (7/24/99)
//      - put back a thread to watch for 'q' on receiver to quit properly before sender's done
//      - added control channel to better handle RSVP timeouts, early aborts, etc.
//      - if no calibrations are specified, we calibrate based on all buffers
//      - gracefully exit if LogRecord runs out of memory, saving all logs we've got so far
//      - changed default behavior so raw file is dumped with no normalization whatsoever.
//      - improved the way anomalous points are caught in clock-skew calc
//   0.9.8: (7/28/99)
//      - fixed field assignments & file opening problem on converting raw file to log.
//      - changed latency to be written to file to signed and fixed normalization routine for
//        cases when clocks are orders of magnitude different (underflow error)
//      - added absolute deviation as goodness of fit measure
//      - added routine to look for clock jumps and fix for them (with -k3 option)
//   0.9.9: (8/4/99)
//      - changed format of .sta file to include more useful information and test params
//      - changed Logging scheme so that we are limited by disk space instead of memory
//        (now using a memory mapped file for the log, so the theoretical limit has gone from
//        less than 2GB to 18EB, but we won't ever get that in practice on normal disks)
//      - added -ni option to run indefinitely
//      - added -R##B option to specify tokenrate in bytes
//      - made default not to show dropped packets at console (it only causes more drops)
//      - added -q## option to log only every nth packet
//   1.0.0: (8/6/99)
//      - fixed bug where if a new qtcp receiver is started immediately after a previous
//        instance, it will think "theend" packets are normal packets and AV
//      - added check for the piix4 timer chip and an appropriate warning
//      - using precise incorrect value in FixWackyTimestamps function
//      - added -A option (aggregate data processing of all .sta files in a directory)
//   1.0.1: (8/6/99)
//      - fixed incorrect calculation of send rate with dropped packets
//   1.0.2: (8/23/99)
//      - improved clock skip detection algorithm
//      - fixed a bug in control channel communication of TokenRate
//      - fixed problem with forceshape option when rate is specified in bytes
//   1.0.3: (8/26/99)
//      - fixed SENDERS NO_SENDERS bug
//      - added summary over time to aggregate stats option
//      - changed .sta file format to include number of drops
//      - fixed shaping in best effort servicetype

// ToDo:
//      - add histogram to output in .sta file and on console
//      - add ability to run w/o control channel connection
//      - mark control channel traffic as higher priority
//      - add more aggregate stats (time varying statistics) -- maybe fourier xform

#ifndef lint
static char RCSid[] = "qtcp.c $Revision: 1.0.3 $";
#endif

#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <io.h>
#include <signal.h>
#include <ctype.h>
#include <sys/types.h>
#include <winsock2.h>
#include <qossp.h>
#include <winbase.h>
#include <time.h>
#include <shlwapi.h>

#if defined(_AMD64_)
#include <math.h>
#endif

#include "ioctl.h"

#define CONTROL_PORT 7239

CHAR *VERSION_STRING = "1.0.3";
#define MAX_STRING 255

INT64 MAX_INT64=9223372036854775807;

HANDLE hRSVPMonitor;
DWORD idRSVPMonitor;

INT64 g_BadHalAdjustment = 46869688;  // this is the value on a piix4 chip
SYSTEM_INFO g_si;
char g_szErr[255];
CRITICAL_SECTION g_csLogRecord;
EXCEPTION_RECORD g_erec;

BOOL g_fOtherSideFinished = FALSE;
BOOL g_fReadyForXmit = FALSE;
SOCKET fd;
SOCKET g_sockControl = INVALID_SOCKET;
struct sockaddr_in sinhim;
struct sockaddr_in sinme;
short port = 5003;              // UDP port number
char *host;                     // ptr to name of host
char szHisAddr[MAX_STRING];
char szMyAddr[MAX_STRING];

int trans;                      // 0=receive, !0=transmit mode
int normalize = 0;              // dump raw file after normalizing

char *Name = NULL;              // Name of file for logs
HANDLE hRawFile = NULL;
HANDLE hLogFile = NULL;
HANDLE hStatFile = NULL;
HANDLE hDriver = NULL;	// handle to the timestmp.sys driver

WSADATA WsaData;
WSAEVENT QoSEvents;

SYSTEMTIME systimeStart;
INT64 timeStart;
INT64 time0;
INT64 time1;
INT64 timeElapsed;

CHAR* TheEnd = "TheEnd";
CHAR* ControlledLoad = "CL";
CHAR* Guaranteed = "GR";
CHAR* BestEffort = "BE";
CHAR* NoTraffic = "NT";

LPCTSTR DriverName = TEXT("\\\\.\\Timestmp");

BOOL fWackySender = FALSE;
BOOL fWackyReceiver = FALSE;

typedef struct {
    HANDLE hSocket;
    INT TokenRate;   
    INT MaxSDUSize;
    INT BucketSize;
    INT MinPolicedSize;
    SERVICETYPE dwServiceType;
    CHAR *szServiceType;
    INT buflen;          // length of buffer
    INT nbuf;            // number of buffers to send
    INT64 calibration;
    BOOLEAN UserStamps;  // By default, we use kernel mode timestamping, if available
    BOOLEAN SkipConfirm; // By default, we wait for user confirmation at certain times
    BOOLEAN RESVonly;    // By default, we send data after getting the resv
    int SkewFitMode;     // by default, 0 = no fit, 1 = chisq, 2 = chisq w/outlier removal
                         //   3 = abs dev
    BOOLEAN Wait;        // By default, we wait for a QoS reservation
    BOOLEAN Dummy;       // insert dummy rows in log by default
    BOOLEAN PrintDrops;  // report dropped packets on console
    BOOLEAN ForceShape;  // by default, we do not force shaping on CL flows
    BOOLEAN RateInBytes; // KB by default
    BOOLEAN AggregateStats; // by default, we do not do this
    BOOLEAN ConvertOnly; // by default, we act normally and do not go around converting files
    BOOLEAN NoSenderTimestamps;
    BOOLEAN NoReceiverTimestamps;
    BOOLEAN TimedRun;    // true if we're running for a specified amount of time
    BOOLEAN RunForever;  // run until the person pushes 'q'
    BOOLEAN nBufUnspecified; // true if the user has not specified the -n parameter
    BOOLEAN RandomFiller;// by default, we use random, incompressible filler data
    int LoggingPeriod;   // by default, 1 (log every packet)
} QTCPPARAMS, *PQTCPPARAMS;

QTCPPARAMS g_params;

typedef struct {
    BOOL Done;             // done if true
    int nWritesInProgress; // number of writes outstanding
    int nReadsInProgress;  // number of reads outstanding
    int nBuffersSent;      // number of buffers sent to the device
    int nBuffersReceived;  // number of buffers received from network
    int nBytesTransferred; // number of bytes written to device
} QTCPSTATE, *PQTCPSTATE;

QTCPSTATE g_state;

typedef struct {
    OVERLAPPED Overlapped;
    PVOID pBuffer;
    DWORD BytesWritten;
} IOREQ, *PIOREQ;

#define MAX_PENDING_IO_REQS 64  // number of simultaneous async calls.

// This format is used for the buffer
// transmitted on the wire.
typedef struct _BUFFER_FORMAT{
    INT64 TimeSentUser;
    INT64 TimeReceivedUser;
    INT64 TimeSent;
    INT64 TimeReceived;
    INT64 Latency;
    INT BufferSize;
    INT SequenceNumber;
} BUFFER_FORMAT, *PBUFFER_FORMAT;

// This format is used for the scheduling record
// written based on the received buffers.
typedef struct _LOG_RECORD{
    INT64 TimeSentUser;
    INT64 TimeReceivedUser;
    INT64 TimeSent;
    INT64 TimeReceived;
    INT64 Latency;
    INT BufferSize;
    INT SequenceNumber;
} LOG_RECORD, *PLOG_RECORD;

// The LOG structure is a data abstraction for a log of LOG_RECORDS that uses memory
// mapped files to have a theoretical storage limit of 18EB. It uses two buffers in memory
// along with a watcher thread so that there is no delay when switching from one bit to
// the next.
typedef struct {
    INT64 nBuffersLogged;
    PBYTE pbMapView;           // view of file in Get/SetLogElement functions
    INT64 qwMapViewOffset;     // offset of Get/Set view in file (rounded down to allocation)
    char *szStorageFile;       // the name of the mapped file on disk (so we can delete it)
    HANDLE hStorageFile;       // the memory mapped file on disk
    HANDLE hFileMapping;       // the file mapping object for our storage file
    INT64 qwFileSize;          // the size of the storage file in bytes
} LOG, *PLOG;
LOG g_log;

// The STATS structure keeps a record of overall statistics for the qtcp run
typedef struct {
    char szStaFile[MAX_PATH];
    char szSender[MAX_STRING];
    char szReceiver[MAX_STRING];
    int nBuffers;
    int nTokenRate;
    int nBytesPerBuffer;
    double sendrate;
    double recvrate;
    double median;
    double mean;
    double var;
    double kurt;
    double skew;
    double abdev;
    FILETIME time; 
    int nDrops;
} STATS, *PSTATS;

INT64 totalBuffers;
INT anomalies = 0;
INT SequenceNumber = 0;
INT LastSequenceNumber = -1;

#define bcopy(s, d, c)  memcpy((u_char *)(d), (u_char *)(s), (c))
#define bzero(d, l)     memset((d), '\0', (l))

#define SENDER      1
#define RECEIVER    0

#define SECONDS_BETWEEN_HELLOS 120
// control messages
#define MSGCH_DELIMITER '!'
#define MSGST_RSVPERR "RSVPERR"
#define MSGST_ABORT "ABORT"
#define MSGST_ERROR "ERROR"
#define MSGST_DONE "DONE"
#define MSGST_HELLO "HELLO"
#define MSGST_RATE "RATE"
#define MSGST_SIZE "SIZE"
#define MSGST_NUM "NUM"
#define MSGST_READY "READY"
#define MSGST_VER "VER"

// -------------------
// Function prototypes
// -------------------

VOID
SetDefaults();

VOID
Usage();

BOOLEAN
GoodParams();

VOID
SetupLogs();

VOID
SetupSockets();
	
SOCKET 
OpenQoSSocket();

INT
SetQoSSocket(
    SOCKET fd,
    BOOL Sending);

VOID
WaitForQoS(
    BOOL Sender,
    SOCKET fd);

ULONG
GetRsvpStatus(
    DWORD dwTimeout,
    SOCKET fd);

VOID
PrintRSVPStatus(
    ULONG code);

VOID
DoTransmit();

VOID WINAPI
TransmitCompletionRoutine(
    DWORD dwErrorCode,
    DWORD dwNumberOfBytesTransferred,
    LPOVERLAPPED pOverlapped);

VOID WINAPI
DelimiterSendCompletion(
    DWORD dwErrorCode,
    DWORD dwNumberOfBytesTransferred,
    LPOVERLAPPED pOverlapped);

VOID
FillBuffer(
    CHAR *Cp,
    INT   Cnt);

INT
TimeStamp(
    CHAR *Cp, 
    INT   Cnt);

VOID
DoReceive();

VOID WINAPI
RecvCompletionRoutine(
    DWORD dwErrorCode,
    DWORD dwNumberOfBytesTransferred,
    LPOVERLAPPED pOverlapped);

VOID
LogRecord(CHAR * Buffer);

BOOL CreateLog(PLOG plog, INT64 c);
BOOL GetLogEntry(PLOG plog, PLOG_RECORD prec, INT64 i);
BOOL DestroyLog(PLOG plog);
BOOL SetLogEntry(PLOG plog, PLOG_RECORD prec, INT64 i);
BOOL AddLogEntry(PLOG plog, PLOG_RECORD prec);

UINT64
GetUserTime();

DWORD
MyCreateFile(
    IN PCHAR Name,
    IN PCHAR Extension,
    OUT HANDLE *File);
    
void AggregateStats();

int IndexOfStatRecWith(int rate, int size, INT64 time, PSTATS pStats, int cStats);

BOOL GetStatsFromFile(PSTATS pstats);

VOID
DoStatsFromFile();
    
DWORD
OpenRawFile(
	IN PCHAR Name,
	OUT HANDLE *File);

INT64 ReadSchedulingRecords(HANDLE file);
	
VOID
DoStats();
	
VOID
WriteSchedulingRecords(
    HANDLE File,
    BOOLEAN InsertDummyRows);

void AdvancedStats();

VOID
GenericStats();

VOID
CheckForLostPackets();

VOID
WriteStats(
    UCHAR * HoldingBuffer,
    INT Count);

VOID
NormalizeTimeStamps();

VOID
ClockSkew(
    DOUBLE * Slope,
    DOUBLE * Offset);

BOOLEAN
AnomalousPoint(
    DOUBLE x,
    DOUBLE y);

VOID
AdjustForClockSkew(
    DOUBLE Slope,
    DOUBLE Offset);
    
BOOL FixWackyTimestamps();

// monitor threads
DWORD WINAPI RSVPMonitor (LPVOID lpvThreadParm);    
DWORD WINAPI KeyboardMonitor (LPVOID lpvThreadParm);
DWORD WINAPI ControlSocketMonitor(LPVOID lpvThreadParm);
DWORD WINAPI LogWatcher(LPVOID lpvThreadParm);

// utilities
int SendControlMessage(SOCKET sock, char * szMsg);
void ErrorExit(char *msg, DWORD dwErrorNumber);
UINT64 GetBadHalAdjustment();
//int compare( const void *arg1, const void *arg2 );
int __cdecl compare(const void *elem1, const void *elem2 ) ;
void medfit(double x[], double y[], int N, double *a, double *b, double *abdev);
double mode(const double data[], const int N);
void RemoveDuplicates(int rg[], int * pN);
void RemoveDuplicatesI64(INT64 rg[], int * pN);
#define RoundUp(val, unit) (val + (val % unit))
#define InRange(val, low, high) ((val >= low) && (val < high)) ? TRUE:FALSE
void PrintFlowspec(LPFLOWSPEC lpfs);

VOID __cdecl
main(INT argc,CHAR **argv)
{
    int 		error;
    char 		*stopstring;
    char 		szBuf[MAX_STRING];
	BOOL        b;
	ULONG		bytesreturned;
	
    printf("qtcp version %s\n\n",VERSION_STRING);

    if (GetBadHalAdjustment() == (UINT64)g_BadHalAdjustment) {
        printf("WARNING: This machine has a timer whose frequency matches that of the piix4\n");
        printf("         chip. There is a known bug in the HAL for this timer that causes the\n");
        printf("         timer to jump forward about 4.7 seconds every once in a while.\n");
        printf("         If you notice large jumps in the timestamps from this machine, try\n");
        printf("         running with the -k3 option to attempt to correct for the timer bug.\n\n");
    }    
    
    srand( (unsigned)time( NULL ) ); // seed the random number generator
    timeStart = GetUserTime();
    GetSystemInfo(&g_si);
    error = WSAStartup( 0x0101, &WsaData );
    if (error == SOCKET_ERROR) {
        printf("qtcp: WSAStartup failed %ld:", WSAGetLastError());
    }

    if (argc < 2) Usage();

    Name = malloc(MAX_STRING);
    bzero(Name,MAX_STRING);

    SetDefaults();

    argv++; argc--;
    while( argc>0 && argv[0][0] == '-' )  {
        switch (argv[0][1]) {
            case 'B':
                g_params.BucketSize = atoi(&argv[0][2]);
                break;
            case 'm':
                g_params.MinPolicedSize = atoi(&argv[0][2]);
                break;
            case 'M':
                g_params.MaxSDUSize = atoi(&argv[0][2]);
                break;
            case 'R':
                g_params.TokenRate = (int)strtod(&argv[0][2],&stopstring);
                if (*stopstring == 0) { // normal run
                    g_params.RateInBytes = FALSE;
                    break;
                }
                if (*stopstring == 'B') { // rate is in bytes / sec, not kbytes/sec
                    g_params.RateInBytes = TRUE;
                    break;
                }
                else {
                    Usage();
                    break;
                }
            case 'S':
                g_params.szServiceType = &argv[0][2];
                if(!strncmp(g_params.szServiceType, ControlledLoad, 2)){
                    g_params.dwServiceType = SERVICETYPE_CONTROLLEDLOAD;
                break;
                }
                if(!strncmp(g_params.szServiceType, Guaranteed, 2)){
                    g_params.dwServiceType = SERVICETYPE_GUARANTEED;
                break;
                }
				if(!strncmp(g_params.szServiceType, BestEffort, 2)){
				    g_params.dwServiceType = SERVICETYPE_BESTEFFORT;
					g_params.Wait = FALSE;
					break;
			    }
				if(!strncmp(g_params.szServiceType, NoTraffic, 2)){
					g_params.dwServiceType = SERVICETYPE_NOTRAFFIC;
					g_params.Wait = FALSE;
					break;
				}
                fprintf(stderr, "Invalid service type (not CL or GR).\n");
                fprintf(stderr, "Using GUARANTEED service.\n");
                break;
            case 'e':
                g_params.ForceShape = TRUE;
              	break;
            case 'W':
                g_params.Wait = FALSE;
                break;
            case 't':
                trans = 1;
                break;
            case 'f':
                strcpy(Name,&argv[0][2]);
                break;
            case 'F':
                strcpy(Name,&argv[0][2]);
                g_params.ConvertOnly = TRUE;
              	break;
            case 'A':
                strcpy(Name,&argv[0][2]);
                g_params.AggregateStats = TRUE;
                break;
            case 'r':
                trans = 0;
                break;
            case 'n':
                g_params.nbuf = (INT)strtod(&argv[0][2],&stopstring);
                if (*stopstring == 0) { // normal run
                    g_params.nBufUnspecified = FALSE;
                    break;
                }
                if (*stopstring == 'i') { // run for an infinite time
                    g_params.RunForever = TRUE;
                    break;
                }
                if (*stopstring == 's') { // run for a specified time
                    g_params.TimedRun = TRUE;
                    printf("Running for %d seconds\n",g_params.nbuf);
                    break;
                }
                else {
                    Usage();
                    break;
                }
            case 'c':
                g_params.calibration = atoi(&argv[0][2]);
                break;
		    case 'k':
		        g_params.SkewFitMode = atoi(&argv[0][2]);
		        if (g_params.SkewFitMode < 0 || g_params.SkewFitMode > 3)
		            ErrorExit("Invalid Skew Fit Mode",g_params.SkewFitMode);
		        break;
            case 'l':
                g_params.buflen = atoi(&argv[0][2]);
                break;
            case 'p':

                port = (short)atoi(&argv[0][2]);


                break;
            case 'd':
                g_params.Dummy = TRUE;
                break;
            case 'N':
                normalize = 1;
                break;
            case 'P':
                g_params.PrintDrops = TRUE;
                break;
			case 'v':
			    g_params.RESVonly = TRUE;
				break;
			case 'y':
				g_params.SkipConfirm = TRUE;
				break;
			case 'u':
			    g_params.UserStamps = TRUE;
			    break;
			case 'i':
			    g_params.RandomFiller = FALSE;
			    break;
			case 'q':
                g_params.LoggingPeriod = atoi(&argv[0][2]);
			    break;
            default:
                Usage();
        }
        argv++; 
        argc--;
    }

	//
	// Make an ioctl to Timestmp driver, if its exists about the 
	// port to timestamp on.
	//
	printf("Trying to open %s\n", DriverName);
				
    hDriver = CreateFile(DriverName,
                         GENERIC_READ | GENERIC_WRITE, 
                         FILE_SHARE_READ | FILE_SHARE_WRITE,
                         0,                     // Default security
                         OPEN_EXISTING,
                         0,  
                         0);                    // No template
   	if(hDriver == INVALID_HANDLE_VALUE) {
	            
		printf("Timestmp.sys CreateFile- Error %ld - Maybe its not INSTALLED\n", GetLastError());
		// Otherwise, print success and close the driver
		
	} else {

       	printf("Timestmp.sys - CreateFile Success.\n");

		b = DeviceIoControl(
					  		hDriver,              			// handle to a device, file, or directory 
							IOCTL_TIMESTMP_REGISTER_PORT,   // control code of operation to perform
							&port,                          // pointer to buffer to supply input data
							2, //nInBufferSize,         	// size, in bytes, of input buffer
							NULL, //lpOutBuffer,            // pointer to buffer to receive output data
							0, //nOutBufferSize,        	// size, in bytes, of output buffer
							&bytesreturned, 				// pointer to variable to receive byte count
                            NULL                            // pointer to overlapped structure
							);

		printf("IOCTL performed\n");
		
		if (!b) {

			printf("IOCTL FAILED!\n", GetLastError());
          	// Close the driver
           	CloseHandle(hDriver);
    	            	
		} else {
			printf("IOCTL succeeded!\n");
		}
   	}
	
    // get the host address if we're the sender
    if(trans)  {
        if(argc != 1)
            Usage();
        host = malloc(strlen(argv[0]) + 1);
        strcpy(host,argv[0]);
    }
        
	// first, we see if this is a conversion -- if it is, just jump right in, else go on
	if (g_params.ConvertOnly) {
		DoStatsFromFile();
		exit(0);
	}

    // see if we're supposed to do stat aggregation
    if (g_params.AggregateStats) {
        AggregateStats();
        exit(0);
    }
    
    // Do argument sanity tests & set default values if not already set
    if (!GoodParams()) exit(1); 

    // Spawn off the control socket monitor thread
    CreateThread(NULL, 0, ControlSocketMonitor, (LPVOID)host, 0, NULL);

    // Get the sockets ready, set for QoS, and wait for a connection
    SetupSockets();

    // Check for a RESV only session
	if (g_params.RESVonly) {  // keep socket open and hang out
		fprintf(stdout, "RSVP connection established. Press return to quit.\n");
		while(TRUE){
			if(getchar())
				break;
		}
		exit(0);
	}

    // start up the RSVPMonitor and keyboard monitor threads to watch for wackiness
    hRSVPMonitor = CreateThread(NULL,0,RSVPMonitor,NULL,0,&idRSVPMonitor);
    CreateThread(NULL,0,KeyboardMonitor,NULL,0,NULL);

    // wait for the control channel to be set up, if it's not already
    while (g_sockControl == INVALID_SOCKET) Sleep(50);

    if (!trans) { // we want to make sure these are not initialized, so we don't put wrong values in .sta
        g_params.buflen = 0;
        g_params.nbuf = 2048; // it's ok to init this because it's not saved in .sta
        g_params.TokenRate = 0;
    }
    
    totalBuffers = g_params.nbuf + g_params.calibration;
    // Tell the receiver the important parameters
    if (trans) {
        if (g_params.RunForever) totalBuffers = 2048;
        sprintf(szBuf, "%s %d", MSGST_NUM, totalBuffers);
        SendControlMessage(g_sockControl, szBuf);
        sprintf(szBuf, "%s %d", MSGST_SIZE, g_params.buflen);
        SendControlMessage(g_sockControl, szBuf);
        if (g_params.RateInBytes) sprintf(szBuf, "%s %d", MSGST_RATE, g_params.TokenRate);
        else sprintf(szBuf, "%s %d", MSGST_RATE, 1000 * g_params.TokenRate);
        SendControlMessage(g_sockControl, szBuf);
    }

    while (!g_fReadyForXmit) Sleep(50);
    
    // If we're the receiver, set up the log buffer and files
    if((Name != NULL) && !trans){
        SetupLogs();
    }

    // Let the user know what's up
    if(trans){
        fprintf(stdout, "qtcp TRANSMITTER\n");
        if (g_params.calibration)
            fprintf(stdout, "\tSending %d calibration buffers.\n", g_params.calibration);
        fprintf(stdout, "\tSending %d buffers of length %d.\n", g_params.nbuf, g_params.buflen);
        fprintf(stdout, "\tDestination address (port) is %s (%d).\n", argv[0], port);
        if (g_params.RateInBytes)
            fprintf(stdout, "\tToken rate is %d bytes/sec.\n", g_params.TokenRate);
        else
            fprintf(stdout, "\tToken rate is %d Kbytes/sec.\n", g_params.TokenRate);
        fprintf(stdout, "\tBucket size is %d bytes.\n", g_params.BucketSize);
    }
    else{
        fprintf(stdout, "qtcp RECEIVER\n");
        if (g_params.calibration)
            fprintf(stdout, "\tPrepared to receive %d calibration buffers.\n", g_params.calibration);
        if (!g_params.nBufUnspecified) {
            fprintf(stdout, "\tPrepared to receive %d buffers.\n", g_params.nbuf); 
        }
    }
    
    // Do the actual communication
    time0 = GetUserTime();
    
    if (trans)
        DoTransmit();
    else
        DoReceive();
        
    time1 = GetUserTime();
    timeElapsed = (time1 - time0)/10000;

    // tell the other guy we're done
    SendControlMessage(g_sockControl, MSGST_DONE);
    
    // get to a new line
    printf("\n");

    // put some stats on the transmitter console
    if (trans) {
        printf("Sent %ld bytes in %I64d milliseconds = %I64d KBps\n", 
            g_state.nBytesTransferred, timeElapsed, g_state.nBytesTransferred/timeElapsed);
    }

    // wait for the other side to tell us it's done.
    while (!g_fOtherSideFinished) Sleep(50);
    
    // let the user know if timestmp.sys was installed
    if (g_params.NoSenderTimestamps && g_params.NoReceiverTimestamps)
        printf("WARNING: No kernel-level timestamps detected on sender or receiver\n\tUsing user-mode timestamps.\n");
    else if (g_params.NoSenderTimestamps)
        printf("WARNING: No kernel-level timestamps detected on sender\n\tUsing user-mode timestamps.\n");
    else if (g_params.NoReceiverTimestamps)
        printf("WARNING: No kernel-level timestamps detected on receiver\n         Using user-mode timestamps.\n");
    

    // Close down the sockets
    if (closesocket((SOCKET)g_params.hSocket) != 0)
        fprintf(stderr,"closesocket failed: %d\n",WSAGetLastError());

    if(timeElapsed <= 100){
        fprintf(stdout,
                "qtcp %s:Time interval too short for valid measurement!\n",
                trans?"-t":"-r");
    }

    // Close files & exit
    if(!trans && Name != NULL){
        if (g_log.nBuffersLogged) {
            DoStats();
        } else {
            printf("ERROR: no buffers logged due to errors.\n");
        }
        CloseHandle(hRawFile);
        CloseHandle(hLogFile);
        CloseHandle(hStatFile);
        DestroyLog(&g_log);
    }
    
    if (WSACleanup() != 0)
        fprintf(stderr,"WSACleanup failed: %d\n",WSAGetLastError());
        
    printf("\n");
    _exit(0);
}  // main()

VOID SetDefaults()
{
    g_params.hSocket = NULL;
    g_params.TokenRate = 100;
    g_params.MaxSDUSize = QOS_NOT_SPECIFIED;
    g_params.BucketSize = QOS_NOT_SPECIFIED;
    g_params.MinPolicedSize = QOS_NOT_SPECIFIED;
    g_params.dwServiceType = SERVICETYPE_GUARANTEED;
    g_params.szServiceType = "GR";
    g_params.buflen = 1472;              /* length of buffer */
    g_params.nbuf = 2 * 1024;            /* number of buffers to send */
    g_params.calibration = 0;
    g_params.UserStamps = FALSE;  // By default, we use kernel mode timestamping, if available
    g_params.SkipConfirm = FALSE; // By default, we wait for user confirmation at certain times
    g_params.SkewFitMode = 2;     // by default, we use absolute deviation
    g_params.Wait = TRUE;         // By default, we wait for a QoS reservation
    g_params.Dummy = FALSE;       // insert dummy rows in log by default
    g_params.PrintDrops = FALSE;   // report dropped packets on console
    g_params.ForceShape = FALSE;  // by default, we do not force shaping on CL flows
    g_params.RateInBytes = FALSE; // KB by default
    g_params.ConvertOnly = FALSE; // by default, we act normally and do not go around converting files
    g_params.AggregateStats = FALSE;
    g_params.NoSenderTimestamps = FALSE;
    g_params.NoReceiverTimestamps = FALSE;
    g_params.TimedRun = FALSE;    // by default, we run for a number of packets
    g_params.RunForever = FALSE;  // by default, we run fora  number of packets
    g_params.nBufUnspecified = TRUE;
    g_params.RandomFiller = TRUE; // by default, we use random filler to prevent compression
    g_params.LoggingPeriod = 1;
} // SetDefaults()

VOID Usage()
{
    fprintf(stderr,"Usage: qtcp [-options] -t host\n");
    fprintf(stderr,"       qtcp [-options] -r\n");
    fprintf(stderr," -t options:\n");
    fprintf(stderr,"        -B##    TokenBucket size signaled to network and to traffic control\n"); 
    fprintf(stderr,"                (default is equal to buffer size)\n");
    fprintf(stderr,"        -m##    MinPolicedSize signaled to network and to traffic control\n");
    fprintf(stderr,"                (default is equal to buffer size)\n");
    fprintf(stderr,"        -R##    TokenRate in kilobytes per second (default is 100 KBPS)\n");
    fprintf(stderr,"        -R##B   TokenRate in bytes per second\n");
    fprintf(stderr,"        -e      Force shaping to TokenRate.\n");
    fprintf(stderr,"        -M      MaxSDUSize to be used in signaling messages (default is buffer\n");
    fprintf(stderr,"                size\n");
    fprintf(stderr,"        -l##    length of buffers to transmit (default is 1472 bytes)\n");
    fprintf(stderr,"        -n##    number of source bufs written to network (default is 2048 bytes)\n");
    fprintf(stderr,"        -n##s   numbef of seconds to send buffers for (numbef of buffers will\n");
    fprintf(stderr,"                be calculated based on other parameters\n");
    fprintf(stderr,"        -ni     run indefinitely (will stop when 'q' is pressed on either)\n");
    fprintf(stderr,"        -c##    Specifies number of calibration packets to be sent\n");
    fprintf(stderr,"                Calibration packets will be sent immediately\n"); 
    fprintf(stderr,"                After calibration packets are sent, n additional\n");
    fprintf(stderr,"                packets will be sent. This option is useful if you want to\n");
    fprintf(stderr,"                change network conditions after a set calibration phase\n");
	fprintf(stderr,"        -y      skip confirmation message after calibration phase\n");
    fprintf(stderr,"        -p##    port number to send to or listen at (default 5003)\n");
    fprintf(stderr,"        -i      use more compressible buffer data\n");
    fprintf(stderr," -r options:\n");
    fprintf(stderr,"        -f\"filename\"    Name prefix to be used in generating log file and\n");
    fprintf(stderr,"                statistics summary. (no file generated by default)\n");
    fprintf(stderr,"        -c##    Specifies number of buffers to use in clock-skew calibration\n");
	fprintf(stderr,"        -k0     do not calculate clock skew\n");
	fprintf(stderr,"        -k1     use chi squared as goodness of fit\n");
	fprintf(stderr,"        -k2     use absolute deviation as goodness of fit (default)\n");
	fprintf(stderr,"        -k3     use abs dev and check for clock jumps\n");
    fprintf(stderr,"        -d      suppress insertion of dummy log records for lost packets.\n");
    fprintf(stderr,"        -N      Normalize before dumping raw file (default is after)\n");
    fprintf(stderr,"        -P      enables console reporting of dropped packets\n");
    fprintf(stderr,"        -u      use user mode timestamps instead of kernel timestamps in logs\n");
    fprintf(stderr,"        -q##    log only every ##th packet\n");
    fprintf(stderr," common options:\n");
    fprintf(stderr,"        -S\"service type\" (CL or GR -- GR is default)\n");
    fprintf(stderr,"        -W      Suppress waiting for QoS reservation\n");
	fprintf(stderr,"        -v      Set up QoS reservation only, send no data\n");
    fprintf(stderr,"        -F\"filename\"  Name prefix of raw file to be converted to log file\n");
    fprintf(stderr,"        -A\"path\"      Path to directory for aggregate statistics computation\n");

    WSACleanup();
    exit(1);
} // Usage()

BOOLEAN GoodParams()
{          
    BOOLEAN ok = TRUE;
    
    if(g_params.buflen < sizeof(BUFFER_FORMAT)){
        printf("Buffer size too small for record!\n");
        ok = FALSE;
    }

    // Unless otherwise specified, min policed size will be equal to 
    // buflen.
    
    if(g_params.MinPolicedSize == QOS_NOT_SPECIFIED){
        g_params.MinPolicedSize = g_params.buflen;
    }
    
    // Same goes for bucket size
    
    if(g_params.BucketSize == QOS_NOT_SPECIFIED){
        g_params.BucketSize = g_params.buflen;
    }

    // And for MaxSDU
    
    if(g_params.MaxSDUSize == QOS_NOT_SPECIFIED){
        g_params.MaxSDUSize = g_params.buflen;
    }

    // If the bucket size is smaller than the buffer size,
    // and this is a sender, then warn the user because 
    // data will be discarded
    
    if((g_params.BucketSize < g_params.buflen) && trans){
        printf("Token bucket size is smaller than buffer size!\n");
        ok = FALSE;
    }

    if(g_params.MaxSDUSize < g_params.buflen){
        printf("MaxSDU cannot be less than buffer size!\n");
        ok = FALSE;
    }

    if(g_params.buflen < 5){
        g_params.buflen = 5;   // send more than the sentinel size
    }

    if(g_params.TimedRun) {
        if (g_params.RateInBytes)
            g_params.nbuf = g_params.nbuf * g_params.TokenRate / g_params.buflen;
        else
            g_params.nbuf = g_params.nbuf * g_params.TokenRate * 1000 / g_params.buflen;
        printf("Using %d buffers\n",g_params.nbuf);
    }

    return ok;
} // GoodParams()

VOID SetupLogs()
{
    CreateLog(&g_log, totalBuffers);

    // set up logging files
    if(ERROR_SUCCESS != MyCreateFile(Name,".raw",&hRawFile)){
        fprintf(stderr, "WARNING: Could not create raw file.\n");
    } 
    
    if(ERROR_SUCCESS == MyCreateFile(Name,".log", &hLogFile)){
        fprintf(stdout,"Logging per-packet data to %s.log.\n",Name);
    }
    else{
        fprintf(stderr, "WARNING: Could not create log file.\n");
    }

    if(ERROR_SUCCESS == MyCreateFile(Name, ".sta", &hStatFile)){
        fprintf(stdout,"Writing statistics sumary to %s.sta\n",Name);
    }
    else{
        fprintf(stderr,"Could not create statistics file.\n");
    }
} // SetupLogs()

VOID SetupSockets() 
{
    struct hostent *addr;
    ULONG addr_tmp;
    char szAddr[MAX_STRING];
    int dwAddrSize, dwError;

        
    // Set address and port parameters 
    if(trans)  {
        bzero((char *)&sinhim, sizeof(sinhim));
        if (atoi(host) > 0 )  {
            sinhim.sin_family = AF_INET;
            sinhim.sin_addr.s_addr = inet_addr(host);
        } 
        else{
            if ((addr=gethostbyname(host)) == NULL){
                printf("ERROR: bad hostname\n");
                WSACleanup();
                exit(1);
            }
            sinhim.sin_family = addr->h_addrtype;
            bcopy(addr->h_addr,(char*)&addr_tmp, addr->h_length);
            sinhim.sin_addr.s_addr = addr_tmp;
        }

        sinhim.sin_port = htons(port);
        sinme.sin_port = 0;             /* free choice */
    } 
    else{
        sinme.sin_port =  htons(port);
    }

    sinme.sin_family = AF_INET;

    // Open socket for QoS traffic
    fd = OpenQoSSocket();

    if((fd == (UINT_PTR)NULL) || (fd == INVALID_SOCKET)){
        fprintf(stderr,"Failed to open QoS socket!\n");
        exit(1);
    }

    // Prepare to get QoS notifications

    if((QoSEvents = WSACreateEvent()) == WSA_INVALID_EVENT){
        fprintf(stderr,
                "Failed to create an event for QoS notifications %ld\n",
                WSAGetLastError());
        exit(1);
    }

    if(WSAEventSelect(fd, QoSEvents, FD_QOS) == SOCKET_ERROR){
        fprintf(stderr,
                "Unable to get notifications for QoS events. %ld\n",
                WSAGetLastError());
    }

    if(trans){
        // Set QoS on sending traffic
        if(SetQoSSocket(fd, TRUE)){
            exit(1);
        }

        fprintf(stdout, "Initiated QoS connection. Waiting for receiver.\n");

        WaitForQoS(SENDER, fd);
    }
    else{ // we're the receiver, so bind and wait
        if(bind(fd, (PSOCKADDR)&sinme, sizeof(sinme)) < 0){
            printf("bind() failed: %ld\n", GetLastError( ));
        }

        if(SetQoSSocket(fd, FALSE)){
            exit(1);
        }

        fprintf(stdout, "Waiting for QoS sender to initiate QoS connection.\n");

        WaitForQoS(RECEIVER, fd);
    }

    // set some options
    // none to set!

    g_params.hSocket = (HANDLE)fd;
} // SetupSockets()

SOCKET 
OpenQoSSocket(
    )
{
    INT bufferSize = 0;
    INT numProtocols;
    LPWSAPROTOCOL_INFO installedProtocols, qosProtocol; 
    INT i;
    SOCKET fd;
    BOOLEAN QoSInstalled = FALSE;

    // Call WSAEnumProtocols to determine buffer size required

    numProtocols = WSAEnumProtocols(NULL, NULL, &bufferSize);

    if((numProtocols != SOCKET_ERROR) && (WSAGetLastError() != WSAENOBUFS)){
        printf("Failed to enumerate protocols!\n");
        return((UINT_PTR)NULL);
    }
    else{
        // Enumerate the protocols, find the QoS enabled one

        installedProtocols = (LPWSAPROTOCOL_INFO)malloc(bufferSize);

        numProtocols = WSAEnumProtocols(NULL,
                                        (LPVOID)installedProtocols,
                                        &bufferSize);

        if(numProtocols == SOCKET_ERROR){
            printf("Failed to enumerate protocols!\n");
            return((UINT_PTR)NULL);
        }
        else{
            qosProtocol = installedProtocols;

            for(i=0; i<numProtocols; i++){
                if((qosProtocol->dwServiceFlags1 & XP1_QOS_SUPPORTED)&&
                   (qosProtocol->dwServiceFlags1 & XP1_CONNECTIONLESS) &&
                   (qosProtocol->iAddressFamily == AF_INET)){
                        QoSInstalled = TRUE;
                        break;
                }
                qosProtocol++;
            }
        }

        // Now open the socket.

        if (!QoSInstalled) {
            fprintf(stderr,"ERROR: No QoS protocols installed on this machine\n");
            exit(1);
        }

        fd = WSASocket(0, 
                       SOCK_DGRAM, 
                       0, 
                       qosProtocol, 
                       0, 
                       WSA_FLAG_OVERLAPPED);

        free(installedProtocols);

        return(fd);
    }
}  // OpenQoSSocket()

INT
SetQoSSocket(
    SOCKET fd,
    BOOL Sending)
{
    QOS qos;
    INT status;
    LPFLOWSPEC flowSpec;
    INT dummy;

    INT receiverServiceType = Sending?
                              SERVICETYPE_NOTRAFFIC:
                              g_params.dwServiceType;

    qos.ProviderSpecific.len = 0;
    qos.ProviderSpecific.buf = 0;

    // receiving flowspec is either NO_TRAFFIC (on a sender) or all
    // defaults except for the service type (on a receiver)

    flowSpec = &qos.ReceivingFlowspec;

    flowSpec->TokenRate = QOS_NOT_SPECIFIED;
    flowSpec->TokenBucketSize = QOS_NOT_SPECIFIED;
    flowSpec->PeakBandwidth = QOS_NOT_SPECIFIED;
    flowSpec->Latency = QOS_NOT_SPECIFIED;
    flowSpec->DelayVariation = QOS_NOT_SPECIFIED;
    flowSpec->ServiceType = receiverServiceType;
    flowSpec->MaxSduSize = QOS_NOT_SPECIFIED;
    flowSpec->MinimumPolicedSize = QOS_NOT_SPECIFIED;

    // now do the sending flowspec

    flowSpec = &qos.SendingFlowspec;

    if(Sending){
        if (g_params.RateInBytes)
            flowSpec->TokenRate = g_params.TokenRate;
        else
            flowSpec->TokenRate = g_params.TokenRate * 1000;
        flowSpec->TokenBucketSize = g_params.BucketSize; 
        

        if (g_params.ForceShape) {
            if (g_params.RateInBytes)
                flowSpec->PeakBandwidth = g_params.TokenRate;
    	    else
    	        flowSpec->PeakBandwidth = g_params.TokenRate * 1000;
    	}
        else 
	        flowSpec->PeakBandwidth = QOS_NOT_SPECIFIED;
        flowSpec->Latency = QOS_NOT_SPECIFIED;
        flowSpec->DelayVariation = QOS_NOT_SPECIFIED;
        flowSpec->ServiceType = g_params.dwServiceType;
        
        if (g_params.ForceShape && flowSpec->ServiceType == SERVICETYPE_BESTEFFORT )
            flowSpec->ServiceType = SERVICETYPE_GUARANTEED | SERVICE_NO_QOS_SIGNALING;

        flowSpec->MaxSduSize = g_params.MaxSDUSize;
        flowSpec->MinimumPolicedSize = g_params.MinPolicedSize;

        printf("Sending Flowspec\n");
        PrintFlowspec(&qos.SendingFlowspec);
        
        status = WSAConnect(fd,
                            (PSOCKADDR)&sinhim,
                            sizeof(sinhim),
                            NULL,
                            NULL,
                            &qos,
                            NULL);
        if(status){
            printf("SetQoS failed on socket: %ld\n", WSAGetLastError());
        }
    }
    else{
        flowSpec->TokenRate = QOS_NOT_SPECIFIED;
        flowSpec->TokenBucketSize = QOS_NOT_SPECIFIED;
        flowSpec->PeakBandwidth = QOS_NOT_SPECIFIED;
        flowSpec->Latency = QOS_NOT_SPECIFIED;
        flowSpec->DelayVariation = QOS_NOT_SPECIFIED;
        flowSpec->ServiceType = SERVICETYPE_NOTRAFFIC;
        flowSpec->MaxSduSize = QOS_NOT_SPECIFIED;
        flowSpec->MinimumPolicedSize = QOS_NOT_SPECIFIED;

        status = WSAIoctl(fd,
                          SIO_SET_QOS,
                          &qos,
                          sizeof(QOS),
                          NULL,
                          0,
                          &dummy,
                          NULL,
                          NULL);
        if(status){
            printf("SetQoS failed on socket: %ld\n", WSAGetLastError());
        }
    }
    
    return(status);
} // SetQoSSocket()
   
VOID
WaitForQoS(
    BOOL Sender,
    SOCKET fd)
{
    ULONG status;

    if(!g_params.Wait){
        // For best effort, we don't do anything QoS... Return
        // right away. In this case, the sender should be started
        // after the reciever, since there is no synchronization
        // via rsvp and data could be missed.

        fprintf(stdout, "WARNING: Not waiting for QoS reservation.\n");
        return;
    }
        
    while(TRUE){
        // get the statuscode, waiting for as long as it takes
        status = GetRsvpStatus(WSA_INFINITE,fd);

        switch (status) {
            case WSA_QOS_RECEIVERS:      // at least one RESV has arrived 
                if (Sender)
                    fprintf(stdout, "QoS reservation installed for %s service.\n", g_params.szServiceType);
                break;
            case WSA_QOS_SENDERS:        // at least one PATH has arrived 
                if (!Sender)
                    fprintf(stdout, "QoS sender detected using %s service.\n", g_params.szServiceType);
                break;
            default:
                PrintRSVPStatus(status);
                break;
        }

        // if we received one of the coveted status codes, break out
        // altogether. otherwise wait and see if we get another batch
        // of indications.
        if( ((status == WSA_QOS_RECEIVERS) && Sender) ||
            ((status == WSA_QOS_SENDERS) && !Sender) ) {
            break;
        }
    }
} // WaitForQoS()

ULONG
GetRsvpStatus(
    DWORD dwTimeout,
    SOCKET fd)
{
    LPQOS   qos;
    UCHAR   qosBuffer[500];
    LPRSVP_STATUS_INFO rsvpStatus;
    INT bytesReturned;
    
    qos = (LPQOS)qosBuffer;
    qos->ProviderSpecific.len = sizeof(RSVP_STATUS_INFO);
    qos->ProviderSpecific.buf = (PUCHAR)(qos+1);
    
    // wait for notification that a QoS event has occured
    WSAWaitForMultipleEvents(1,
                            &QoSEvents,
                            FALSE,
                            dwTimeout,
                            TRUE);

    // loop through all qos events
    WSAIoctl(fd,
             SIO_GET_QOS,
             NULL,
             0,
             qosBuffer,
             sizeof(qosBuffer),
             &bytesReturned,
             NULL,
             NULL);

    rsvpStatus = (LPRSVP_STATUS_INFO)qos->ProviderSpecific.buf;
    
    return rsvpStatus->StatusCode;
} // GetRsvpStatus

VOID
PrintRSVPStatus(ULONG code) 
{    
    switch (code) {
        case WSA_QOS_RECEIVERS:             // at least one RESV has arrived 
            printf("WSA_QOS_RECEIVERS\n");
            break;
        case WSA_QOS_SENDERS:               // at least one PATH has arrived 
            printf("WSA_QOS_SENDERS\n");
            break;
        case WSA_QOS_REQUEST_CONFIRMED:     // Reserve has been confirmed
            printf("WSA_QOS_REQUEST_CONFIRMED\n"); 
            break;
        case WSA_QOS_ADMISSION_FAILURE:     // error due to lack of resources
            printf("WSA_QOS_ADMISSION_FAILURE\n"); 
            break;
        case WSA_QOS_POLICY_FAILURE:        // rejected for admin reasons
            printf("WSA_QOS_POLICY_FAILURE\n"); 
            break;
        case WSA_QOS_BAD_STYLE:             // unknown or conflicting style
            printf("WSA_QOS_BAD_STYLE\n"); 
            break;
        case WSA_QOS_BAD_OBJECT:            // problem with some part of the 
                                            // filterspec/providerspecific 
                                            // buffer in general 
            printf("WSA_QOS_BAD_OBJECT\n"); 
            break;
        case WSA_QOS_TRAFFIC_CTRL_ERROR:    // problem with some part of the 
                                            // flowspec
            printf("WSA_QOS_TRAFFIC_CTRL_ERROR\n"); 
            break;
        case WSA_QOS_GENERIC_ERROR:         // general error 
            printf("WSA_QOS_GENERIC_ERROR\n");
            break;
        default:
            printf("Unknown RSVP StatusCode %lu\n", code); 
            break;
    }
} // PrintRSVPStatus


VOID
DoTransmit()
{
    IOREQ IOReq[MAX_PENDING_IO_REQS] = { 0 };
    INT i;
    BOOL ret;
    BOOL fOk;

    g_state.nBytesTransferred = 0;
    g_state.nBuffersSent = 0;
    g_state.nWritesInProgress = 0;

    // fill up the initial buffers and send them on their way    
    for (i=0; i<MAX_PENDING_IO_REQS; i++) {
        IOReq[i].pBuffer = malloc(g_params.buflen);
        FillBuffer(IOReq[i].pBuffer,g_params.buflen);
        TimeStamp(IOReq[i].pBuffer,g_params.buflen);
        IOReq[i].Overlapped.Internal = 0;
        IOReq[i].Overlapped.InternalHigh = 0;
        IOReq[i].Overlapped.Offset = 0;
        IOReq[i].Overlapped.OffsetHigh = 0;
        IOReq[i].Overlapped.hEvent = NULL;

        if (g_state.nBuffersSent < totalBuffers) {
            WriteFileEx(g_params.hSocket,
                        IOReq[i].pBuffer,
                        g_params.buflen,
                        &IOReq[i].Overlapped,
                        TransmitCompletionRoutine);

            g_state.nWritesInProgress++;
            g_state.nBuffersSent++;
        }
    } 

    // now loop until an error happens or we're done writing to the socket
    while (g_state.nWritesInProgress > 0) {
        SleepEx(INFINITE, TRUE);
    }

    // send the end of transmission delimiters
    for (i=0; i<MAX_PENDING_IO_REQS; i++) {
        strncpy(IOReq[i].pBuffer,TheEnd,strlen(TheEnd));
        fOk = WriteFileEx(g_params.hSocket,
                    IOReq[i].pBuffer,
                    strlen(TheEnd),
                    &IOReq[i].Overlapped,
                    DelimiterSendCompletion);
        g_state.nWritesInProgress++;

        if (!fOk) {
            printf("WriteFileEx() failed: %lu\n",GetLastError());
        }

    }

    // wait for all the delimiters to be sent
    while (g_state.nWritesInProgress > 0) {
        SleepEx(INFINITE, TRUE);
    }

    // free up the used memory
    for (i=0; i<MAX_PENDING_IO_REQS; i++) {
        free(IOReq[i].pBuffer);
    }
} // DoTransmit()

VOID WINAPI
TransmitCompletionRoutine(
    DWORD dwErrorCode,
    DWORD dwNumberOfBytesTransferred,
    LPOVERLAPPED pOverlapped)
{
    PIOREQ pIOReq = (PIOREQ) pOverlapped;
    BOOL fOk;
    
    if (dwErrorCode == ERROR_REQUEST_ABORTED) {
        g_state.Done = TRUE;
    }
    else if (dwErrorCode != NO_ERROR) {
        printf("ERROR: Write completed abnormally: %u\n",dwErrorCode);
    }

    g_state.nWritesInProgress--;
    g_state.nBytesTransferred += dwNumberOfBytesTransferred;

    // check to make sure we're not done
    if (g_state.Done)
        return;

    // give some indication of life
    if(!(g_state.nBuffersSent % 100)){
        fprintf(stdout, ".");
    }

    // if there are more buffers to go, send one
    if (g_state.nBuffersSent < totalBuffers || g_params.RunForever) {
    
        // see if this was the last of the calibration buffers (if we want confirmation)
        if (g_params.SkipConfirm == FALSE) {
            if (g_params.calibration && (g_state.nBuffersSent == g_params.calibration)) {
                printf("\nCalibration complete. Type 'c' to continue.\n");
                while(TRUE){
                    if(getchar() == 'c'){
                        break;
                    }
                }
            }
        }
    
        // fill in the buffer with new values
        FillBuffer(pIOReq->pBuffer,g_params.buflen);
        TimeStamp(pIOReq->pBuffer,g_params.buflen);

        // send a request to write the new buffer
        fOk = WriteFileEx(g_params.hSocket,
                    pIOReq->pBuffer,
                    g_params.buflen,
                    pOverlapped,
                    TransmitCompletionRoutine);

        if (!fOk) {
            printf("WriteFileEx() failed: %lu\n",GetLastError());
        }

        g_state.nWritesInProgress++;
        g_state.nBuffersSent++;
    }
} // TransmitCompletionRoutine()

VOID WINAPI
DelimiterSendCompletion(
    DWORD dwErrorCode,
    DWORD dwNumberOfBytesTransferred,
    LPOVERLAPPED pOverlapped)
{
    g_state.nWritesInProgress--;
} // DelimiterSendCompletion()

VOID
FillBuffer(
    CHAR *Cp,
    INT   Cnt)
{
    PBUFFER_FORMAT buf = (PBUFFER_FORMAT) Cp;
    CHAR c = 0;
    
    // Fill with a background pattern
    if (g_params.RandomFiller) { // incompressible
        while(Cnt-- > 0) {
            c = rand() % 0x5F;
            c += 0x20;
            *Cp++ = c;
        }
    }
    else { // compressible
        while(Cnt-- > 0){
            while(!isprint((c&0x7F))) c++;
            *Cp++ = (c++&0x7F);
        }
    }

    buf->TimeSent = -1;
    buf->TimeReceived = -1;
} // FillBuffer()

INT
TimeStamp(
    CHAR *Cp, 
    INT   Cnt)
{
    PBUFFER_FORMAT record;
    LARGE_INTEGER timeSent;
    INT64 time;

    record = (BUFFER_FORMAT *)Cp;
        
    // Stamp with length and sequence number
    
    if(Cnt < sizeof(BUFFER_FORMAT)){
        printf("ERROR: Buffer length smaller than record size!\n");
        return(0);
    }
    else{
        time = GetUserTime();
        record->TimeSentUser = time;
        record->BufferSize = Cnt;
        record->SequenceNumber = SequenceNumber++;
    }
    return 1;
} // TimeStamp()

VOID
DoReceive()
{
    IOREQ IOReq[MAX_PENDING_IO_REQS] = { 0 };
    INT i;
    BOOL ret;
    
    // set the start state
    g_state.Done = FALSE;
    g_state.nBytesTransferred = 0;
    g_state.nBuffersReceived = 0;
    g_state.nReadsInProgress = 0;

    // fill up the initial buffers and send them on their way    
    for (i=0; i<MAX_PENDING_IO_REQS; i++) {
        IOReq[i].pBuffer = malloc(g_params.buflen);
        
        IOReq[i].Overlapped.Internal = 0;
        IOReq[i].Overlapped.InternalHigh = 0;
        IOReq[i].Overlapped.Offset = 0;
        IOReq[i].Overlapped.OffsetHigh = 0;
        IOReq[i].Overlapped.hEvent = NULL;

        if (g_state.nBuffersReceived < totalBuffers) {
            ReadFileEx(g_params.hSocket,
                       IOReq[i].pBuffer,
                       g_params.buflen,
                       &IOReq[i].Overlapped,
                       RecvCompletionRoutine);

            g_state.nReadsInProgress++;
        }
    }

    InitializeCriticalSection(&g_csLogRecord);

    // now loop until an error happens or we're done writing to the socket
    while ((g_state.nReadsInProgress > 0) && !g_state.Done) {
        SleepEx(5000, TRUE);
        if (g_state.Done)
            break;
    }
    DeleteCriticalSection(&g_csLogRecord);

    // cancel the other pending reads
    CancelIo(g_params.hSocket);

    // free up the used memory
    for (i=0; i<MAX_PENDING_IO_REQS; i++) {
        free(IOReq[i].pBuffer);
    }
} // DoReceive()

VOID WINAPI
RecvCompletionRoutine(
    DWORD dwErrorCode,
    DWORD dwNumberOfBytesTransferred,
    LPOVERLAPPED pOverlapped)
{
    PIOREQ pIOReq = (PIOREQ) pOverlapped;
    BOOL fOk;
    static BOOL fLastWasError = FALSE;

    g_state.nReadsInProgress--;
    g_state.nBytesTransferred += dwNumberOfBytesTransferred;

    if (dwNumberOfBytesTransferred == 0) { // an error occurred
        if (!fLastWasError) {
            printf("ERROR in RecvCompletionRoutine: code=%d, lasterr=%d\n",
                dwErrorCode, GetLastError());
            printf("\tReceived no data. Telling sender to abort...\n");
            SendControlMessage(g_sockControl, MSGST_ERROR);
        }
        fLastWasError = TRUE;
    }
    else fLastWasError = FALSE;

    // if this is the first packet we've received, save the system time
    if (g_state.nBuffersReceived == 0) {
        GetSystemTime(&systimeStart);
    }

    // give some indication of life
    if(!(g_state.nBuffersReceived % 100)){
        fprintf(stdout, ".");
    }

    // end of transmission delimiter? if so, set the total buffers to the number got
    if(!(strncmp(pIOReq->pBuffer, TheEnd, 6))) {
        totalBuffers = g_state.nBuffersReceived;
        g_state.Done = TRUE;
    }

    // check to see if someone's set our done flag (if they have, leave)
    if (g_state.Done)
        return;

    // if not, then the buffer should hold a scheduling record.
    if(dwNumberOfBytesTransferred>0 && dwNumberOfBytesTransferred <= sizeof(BUFFER_FORMAT)) {
        printf("Buffer too small for scheduling record\n");
        printf("\tOnly %d bytes read.\n", dwNumberOfBytesTransferred);
    }

    // Log the record, but don't log more than one at a time (lock on this call)
    if (dwNumberOfBytesTransferred >= sizeof(BUFFER_FORMAT) && 
            g_state.nBuffersReceived % g_params.LoggingPeriod == 0) {
        EnterCriticalSection(&g_csLogRecord);
        LogRecord(pIOReq->pBuffer);
        LeaveCriticalSection(&g_csLogRecord);
    }

    // if there are more buffers (or if we don't know how many are coming), ask for one
    if ((g_state.nBuffersReceived < totalBuffers) || g_params.nBufUnspecified) {        
        // send a request to read the next buffer
        fOk = ReadFileEx(g_params.hSocket,
                   pIOReq->pBuffer,
                   g_params.buflen,
                   pOverlapped,
                   RecvCompletionRoutine);

        if (!fOk) {
            printf("ReadFileEx() failed: %lu\n",GetLastError());
        }
        
        g_state.nReadsInProgress++;
        g_state.nBuffersReceived++;
    }
} // RecvCompletionRoutine()

void LogRecord(char * Buffer)
{
    // This function copies the recieved record to the scheduling array.
    // The contents of the array are processed and written to file once
    // reception is complete.
 
    PBUFFER_FORMAT inRecord = (PBUFFER_FORMAT)Buffer;
    LOG_RECORD outRecord;
    INT64 time;
    SYSTEMTIME CurrentTime;

    time = GetUserTime();

	outRecord.TimeSentUser = inRecord->TimeSentUser;
	outRecord.TimeReceivedUser = time;
    outRecord.TimeSent = inRecord->TimeSent;
    outRecord.TimeReceived = inRecord->TimeReceived;
    outRecord.BufferSize = inRecord->BufferSize;
    outRecord.SequenceNumber = inRecord->SequenceNumber;

    if (inRecord->TimeSent == -1) {
        outRecord.TimeSent = outRecord.TimeSentUser;
        g_params.NoSenderTimestamps = TRUE;
    }

    if (inRecord->TimeReceived == -1) {
        outRecord.TimeReceived = outRecord.TimeReceivedUser;
        g_params.NoReceiverTimestamps = TRUE;
    }

    if(g_params.UserStamps){
        outRecord.TimeSent = outRecord.TimeSentUser;
        outRecord.TimeReceived = outRecord.TimeReceivedUser;
    }
    outRecord.Latency = outRecord.TimeReceived - outRecord.TimeSent;

    AddLogEntry(&g_log, &outRecord);

    if(g_params.PrintDrops){
        if(inRecord->SequenceNumber != LastSequenceNumber + g_params.LoggingPeriod){
            GetLocalTime(&CurrentTime);

            printf("\n%4d/%02d/%02d %02d:%02d:%02d:%04d: ",
                    CurrentTime.wYear,
                    CurrentTime.wMonth,
                    CurrentTime.wDay,
                    CurrentTime.wHour,
                    CurrentTime.wMinute,
                    CurrentTime.wSecond,
                    CurrentTime.wMilliseconds);

            printf("Dropped %d packets after packet %d.\n",
                    inRecord->SequenceNumber - LastSequenceNumber,
                    LastSequenceNumber);
        }

        LastSequenceNumber = inRecord->SequenceNumber;
    }
    return;
} // LogRecord()

BOOL CreateLog(PLOG plog, INT64 c) {
    // sets up a log structure that can hold c entries
    char szTempFile[MAX_PATH];
    char szTempPath[MAX_PATH];
    SYSTEM_INFO si;
    DWORD dwFileSizeHigh;
    DWORD dwFileSizeLow;
    INT64 qwFileSize;

    // get some system info
    GetSystemInfo(&si);
    
    // allocate logging array
    plog->nBuffersLogged = 0;
    plog->pbMapView = NULL;
    plog->qwMapViewOffset = -1;

    // set up the temporary storage file for logging
    GetTempPath(MAX_PATH, szTempPath);
    GetTempFileName(szTempPath, "qtc", 0, szTempFile);
    plog->szStorageFile = malloc(strlen(szTempFile) + 1);
    strcpy(plog->szStorageFile, szTempFile);
    plog->hStorageFile = CreateFile(szTempFile, GENERIC_READ | GENERIC_WRITE, 0, 
        NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL);
    if (plog->hStorageFile == INVALID_HANDLE_VALUE)
        ErrorExit("Could not create temp storage file",GetLastError());

    // create the memory mapping kernel object
    qwFileSize = c * sizeof(LOG_RECORD);
    dwFileSizeHigh = (DWORD) (qwFileSize >> 32);
    dwFileSizeLow = (DWORD) (qwFileSize & 0xFFFFFFFF);
    plog->qwFileSize = qwFileSize;
    plog->hFileMapping = CreateFileMapping(plog->hStorageFile, NULL, PAGE_READWRITE,
        dwFileSizeHigh,dwFileSizeLow,NULL);
    if (plog->hFileMapping == NULL)
        ErrorExit("Could not create mapping for temp storage file",GetLastError());
    
    return TRUE;
}

BOOL DestroyLog(PLOG plog) {
    DWORD dwError;
    // destroys the log and all associated data
    dwError = CloseHandle(plog->hFileMapping);
    if (!dwError) printf("Error in DestroyLog:CloseHandle(FileMapping) %d\n",GetLastError());
    dwError = CloseHandle(plog->hStorageFile);
    if (!dwError) printf("Error in DestroyLog:CloseHandle(StorageFile) %d\n",GetLastError());
    dwError = UnmapViewOfFile(plog->pbMapView);
    if (!dwError) printf("Error in DestroyLog:UnmapViewOfFile(plog->pbMapView) %d\n",GetLastError());
    dwError = DeleteFile(plog->szStorageFile);
    if (!dwError) printf("Error in DestroyLog:DeleteFile(StroageFile) %d\n",GetLastError());
    free(plog->szStorageFile);
    return FALSE;
}

void PrintLogRecord(PLOG_RECORD prec) {
    char szBuf[MAX_STRING];

    sprintf(szBuf,"%d: %I64u - %I64u (%I64d)",
        prec->SequenceNumber,prec->TimeSent,prec->TimeReceived,prec->Latency);
    puts(szBuf);
}

BOOL ExtendLog(PLOG plog) {
    // makes the log bigger by some fixed constant
    HANDLE hNewFileMapping;
    INT64 qwNewFileSize;
    
    UnmapViewOfFile(plog->pbMapView);

    qwNewFileSize = plog->qwFileSize + g_si.dwAllocationGranularity * sizeof(LOG_RECORD);
    hNewFileMapping = CreateFileMapping(plog->hStorageFile, NULL, PAGE_READWRITE,
                (DWORD)(qwNewFileSize >> 32), (DWORD)(qwNewFileSize & 0xFFFFFFFF), NULL);
    if (hNewFileMapping == NULL) {
        ErrorExit("Could not create mapping for temp storage file",GetLastError());
        return FALSE;
    }
    plog->qwFileSize = qwNewFileSize;
    CloseHandle(plog->hFileMapping);
    plog->hFileMapping = hNewFileMapping;
    plog->qwMapViewOffset = -1;
    return TRUE;
}

BOOL GetLogEntry(PLOG plog, PLOG_RECORD prec, INT64 i) {
    // fills prec with the (0 indexed) i'th log in plog
    // returns TRUE if it was successful, FALSE otherwise
    INT64 qwT;
    PLOG_RECORD entry;

    // first, check to see if this is within the range of our file
    if ((INT64)((i+1)*sizeof(LOG_RECORD)) > plog->qwFileSize) {
        // too high, so we return false
        return FALSE;
    }
    
    // we have to round down to the nearest allocation boundary
    qwT = sizeof(LOG_RECORD) * i;   // offset within file
    qwT /= g_si.dwAllocationGranularity; // in allocation granularity units

    // check to see if we do not already have this mapped in memory
    if (plog->qwMapViewOffset != qwT * g_si.dwAllocationGranularity) {
        if (plog->pbMapView != NULL) UnmapViewOfFile(plog->pbMapView);
        plog->qwMapViewOffset = qwT * g_si.dwAllocationGranularity;  // offset of lower allocation bound  
        if (plog->qwFileSize < (INT64)g_si.dwAllocationGranularity) {
            // file is smaller than allocation granularity
            plog->qwMapViewOffset = 0;
            plog->pbMapView = MapViewOfFile(plog->hFileMapping, FILE_MAP_WRITE, 0, 0, 0);
        }
        else if (plog->qwFileSize - plog->qwMapViewOffset < g_si.dwAllocationGranularity) {
            // we're within an allocation granularity of the end of the file
            plog->pbMapView = MapViewOfFile(plog->hFileMapping, FILE_MAP_WRITE,
                                (DWORD)(plog->qwMapViewOffset >> 32),
                                (DWORD)(plog->qwMapViewOffset & 0xFFFFFFFF), 
                                (DWORD)(plog->qwFileSize - plog->qwMapViewOffset));
        }
        else {
            // we're just somewhere in the file with space around us
            plog->pbMapView = MapViewOfFile(plog->hFileMapping, FILE_MAP_WRITE, 
                                (DWORD)(plog->qwMapViewOffset >> 32),
                                (DWORD)(plog->qwMapViewOffset & 0xFFFFFFFF), 
                                RoundUp(g_si.dwAllocationGranularity,sizeof(LOG_RECORD)));
        }
        if (plog->pbMapView == NULL) 
            ErrorExit("GetLogEntry could not MapViewOfFile",GetLastError());
    }
    qwT = sizeof(LOG_RECORD) * i;
    entry = (PLOG_RECORD)(plog->pbMapView + (qwT - plog->qwMapViewOffset));
    CopyMemory(prec, entry, sizeof(LOG_RECORD));
    return TRUE;
}


BOOL SetLogEntry(PLOG plog, PLOG_RECORD prec, INT64 i) {
    // fills log entry i with the data pointed to by prec
    // returns TRUE if it was successful, FALSE otherwise
    INT64 qwT;
    PLOG_RECORD entry;

    // first, check to see if this is within the range of our file
    if ((INT64)((i+1)*sizeof(LOG_RECORD)) > plog->qwFileSize) {
        // we need to make our mapping bigger
        ExtendLog(plog);
    }
    
    // we have to round down to the nearest allocation boundary
    qwT = sizeof(LOG_RECORD) * i;   // offset within file
    qwT /= g_si.dwAllocationGranularity; // in allocation granularity units

    // check to see if we do not already have this mapped in memory
    if (plog->qwMapViewOffset != qwT * g_si.dwAllocationGranularity) {
        if (plog->pbMapView != NULL) UnmapViewOfFile(plog->pbMapView);
        plog->qwMapViewOffset = qwT * g_si.dwAllocationGranularity;  // offset of lower allocation bound  
        if (plog->qwFileSize < (INT64)g_si.dwAllocationGranularity) {
            // file is smaller than allocation granularity
            plog->qwMapViewOffset = 0;
            plog->pbMapView = MapViewOfFile(plog->hFileMapping, FILE_MAP_WRITE, 0, 0, 0);
        }
        else if (plog->qwFileSize - plog->qwMapViewOffset < g_si.dwAllocationGranularity) {
            // we're within an allocation granularity of the end of the file
            plog->pbMapView = MapViewOfFile(plog->hFileMapping, FILE_MAP_WRITE,
                                (DWORD)(plog->qwMapViewOffset >> 32),
                                (DWORD)(plog->qwMapViewOffset & 0xFFFFFFFF), 
                                (DWORD)(plog->qwFileSize - plog->qwMapViewOffset));
        }
        else {
            // we're just somewhere in the file with space around us
            plog->pbMapView = MapViewOfFile(plog->hFileMapping, FILE_MAP_WRITE, 
                                (DWORD)(plog->qwMapViewOffset >> 32),
                                (DWORD)(plog->qwMapViewOffset & 0xFFFFFFFF), 
                                RoundUp(g_si.dwAllocationGranularity,sizeof(LOG_RECORD)));
        }
        if (plog->pbMapView == NULL) 
            ErrorExit("SetLogEntry could not MapViewOfFile",GetLastError());
    }
    qwT = sizeof(LOG_RECORD) * i;
    entry = (PLOG_RECORD)(plog->pbMapView + (qwT - plog->qwMapViewOffset));

    CopyMemory(entry, prec, sizeof(LOG_RECORD));
    
    return TRUE;
}

BOOL AddLogEntry(PLOG plog, PLOG_RECORD prec) {
    PLOG_RECORD entry;
    // adds the data pointed to by prec to the end of the log
    // returns TRUE if it was successful, FALSE otherwise

    SetLogEntry(plog, prec, plog->nBuffersLogged);

    plog->nBuffersLogged++;

    return TRUE;
}

UINT64
GetUserTime()
{   // This function returns the performance counter time in units of 100ns
    LARGE_INTEGER count, freq;

    NtQueryPerformanceCounter(&count,&freq);
    
    // make sure we have hardware performance counting
    if(freq.QuadPart == 0) {
        NtQuerySystemTime(&count);
        return (UINT64)count.QuadPart;
    }
   
    return (UINT64)((10000000 * count.QuadPart) / freq.QuadPart);
} // GetUserTime()

UINT64
GetBadHalAdjustment() {
    // this function returns the amount the hal timer in a machine with 
    // an intel chipset with the piix4 timer chip will jump forward in the case of
    // repeated garbage returned fom the piix4 (bug #347410) so we can correct it out
    // in the FixWackyTimestamps routine
    LARGE_INTEGER freq;
    UINT64 diff;

    QueryPerformanceFrequency(&freq);
    // so we want to find how much it is increased in 100ns intervals if we increase
    // byte 3 by 1.
    diff   = 0x01000000;
    diff *= 10000000;
    diff  /= (UINT64)freq.QuadPart;
    return diff;
}

DWORD
MyCreateFile(
    IN PCHAR Name,
    IN PCHAR Extension,
    OUT HANDLE *File)
{
    HANDLE hFile;
    UCHAR * fileName;

    fileName = malloc(strlen(Name) + 5);
    bzero(fileName,strlen(Name) + 5);
    strncpy(fileName, Name, strlen(Name));
    if (strlen(Extension)==4) {
        strcat(fileName,Extension);
    }
    else
        return !ERROR_SUCCESS;

    hFile = CreateFile(fileName,
                       GENERIC_WRITE | GENERIC_READ,
                       0,
                       NULL,
                       CREATE_ALWAYS,
                       FILE_ATTRIBUTE_NORMAL,
                       NULL);

    *File = hFile;

    return(INVALID_HANDLE_VALUE == hFile ? (!(ERROR_SUCCESS)) : ERROR_SUCCESS);
} // MyCreateFile()

void AggregateStats() {
    // this will go through the directory specified in Name and aggregate stats from
    // all the .sta files therein. it will then output the results of the aggregation
    // in a file within that directory called stats.qtc
    char szDirPath[3 * MAX_PATH];
    char szSearchString[3 * MAX_PATH];
    WIN32_FIND_DATA FileData;   // Data structure describes the file found
    HANDLE hSearch;             // Search handle returned by FindFirstFile
    PCHAR rgszStaFiles[1000];   // an array of the names of the .sta files
    int cStaFiles = 0, i,j,k,l; // keeps track of how many of the .sta files there are
    STATS * pStats;
    int rgSizes[1000], cSizes = 0;
    int rgRates[1000], cRates = 0;
    char szAggFile[3 * MAX_PATH];
    char szLineBuf[1000];
    STATS statsT;
    FILE *pfile;
    FILETIME rgtime[1000];
    SYSTEMTIME st;
    ULARGE_INTEGER uliT;
    int ctime = 0;
    int cSpecs = 0;

    PathCanonicalize(szDirPath,Name);
    if (szDirPath[strlen(szDirPath) - 1] == '"') szDirPath[strlen(szDirPath) - 1] = 0;
    if (!PathIsDirectory(szDirPath)) {
        printf("Path (%s) is not a directory\n",szDirPath);
        ErrorExit("Invalid Path for aggregate stats", -1);
    }

    // so now szDirPath is the path to the directory we want to go through
    // and we begin our search for .sta files
    sprintf(szSearchString,"%s\\*.sta",szDirPath);
    hSearch = FindFirstFile (szSearchString, &FileData);
    if (hSearch == INVALID_HANDLE_VALUE) {
        ErrorExit("No .sta files found.",GetLastError());
    }
    
    do {
        rgszStaFiles[cStaFiles] = malloc(sizeof(char) * 3 * MAX_PATH);
        // check to see if it's a good .sta file
        sprintf(statsT.szStaFile,"%s\\%s", szDirPath, FileData.cFileName);
        if (GetStatsFromFile(&statsT)) {
            // if it's good, include it
            strcpy(rgszStaFiles[cStaFiles], FileData.cFileName);
            cStaFiles++;
        }
    } while (FindNextFile(hSearch, &FileData));
    if (GetLastError() != ERROR_NO_MORE_FILES) {
        ErrorExit("Problem in FindNextFile()",GetLastError());
    }

    // open the stats file
    sprintf(szAggFile,"%s\\stats.qtc",szDirPath);
    pfile = fopen(szAggFile,"w+");
    if (pfile == NULL) printf("Could not open file for aggregate stats: %s\n",szAggFile);
    
    pStats = malloc(cStaFiles * sizeof(STATS));
    ZeroMemory(pStats, cStaFiles * sizeof(STATS));
    for (i=0; i<cStaFiles; i++) {
        sprintf(pStats[i].szStaFile, "%s\\%s", szDirPath, rgszStaFiles[i]);
        GetStatsFromFile(&(pStats[i]));
    }

    // at this point our pStats array is loaded up, so we can go to work
    for (i=0; i<cStaFiles; i++) {
        rgSizes[i] = pStats[i].nBytesPerBuffer;
        rgRates[i] = pStats[i].nTokenRate;
        rgtime[i] = pStats[i].time;
    }

    // now sort them and get out the dupliates
    cSizes = cRates = ctime = cStaFiles;
    RemoveDuplicates(rgSizes, &cSizes);
    RemoveDuplicates(rgRates, &cRates);
    RemoveDuplicatesI64((INT64 *)rgtime, &ctime);
    // --- do the stats by by time ---
    fprintf(pfile, "Latency Characteristics at varying times\n");
    fprintf(pfile, "                                 Latency Characteristics (microseconds)              Rates (Bps)           Buffers\n");
    fprintf(pfile, "       Time (UTC)            Median      StDev       Mean     Skew     Kurt       Send    Receive   Received    Dropped\n");
    for (i=0; i<cRates; i++) {
        for (j=0; j<cSizes; j++) {
            // print the flowspec
            if (IndexOfStatRecWith(rgRates[i],rgSizes[j],-1,pStats,cStaFiles) != -1) {
                fprintf(pfile, "FLOWSPEC %d: %dB buffers at %d Bps\n",
                    cSpecs++, rgSizes[j], rgRates[i]);
                for (k=0; k<ctime; k++) {
                    // check to see if there is something with these params and print it
                    ZeroMemory(&uliT, sizeof(ULARGE_INTEGER));
                    CopyMemory(&uliT, &rgtime[k], sizeof(ULARGE_INTEGER));
                    l = IndexOfStatRecWith(rgRates[i],rgSizes[j],uliT.QuadPart,pStats,cStaFiles);
                    if (l > 0) {
                        FileTimeToSystemTime(&pStats[l].time, &st);
                        fprintf(pfile,"%02hu/%02hu/%04hu %2hu:%02hu.%02hu.%03hu: %10.1lf %10.1lf %10.1lf %8.2lf %8.2lf %10.1lf %10.1lf %10d %10d\n",
                            st.wMonth, st.wDay, st.wYear, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, 
                            pStats[l].median, sqrt((double)pStats[l].var), pStats[l].mean, 
                            pStats[l].skew, pStats[l].kurt, pStats[l].sendrate, pStats[l].recvrate,
                            pStats[l].nBuffers, pStats[l].nDrops);
                    }
                }
                fprintf(pfile,"\n");
            }
        }
    }

    fprintf(pfile, "Latency Characteristics by flowspec\n");
    // --- do the stats by flowspec ---
    // now write the file, line by line, to szLineBuf, then to the file
    // median
    fprintf(pfile,"Median Latency (microseconds)\n");
    fprintf(pfile,"           ");
    for (i=0; i<cSizes; i++)
        fprintf(pfile,"%9dB ",rgSizes[i]);
    fprintf(pfile,"\n");
    for (i=0; i<cRates; i++) {
        fprintf(pfile,"%7dBps ",rgRates[i]);
        for (j=0; j<cSizes; j++) {
            k = IndexOfStatRecWith(rgRates[i],rgSizes[j],-1,pStats,cStaFiles);
            if (k != -1) {
                fprintf(pfile,"%10.1lf ",pStats[k].median);
            }
            else {
                fprintf(pfile,"           ");
            }
        }
        fprintf(pfile,"\n");
    }
    fprintf(pfile,"\n");
    // mean
    fprintf(pfile,"Mean Latency (microseconds)\n");
    fprintf(pfile,"           ");
    for (i=0; i<cSizes; i++)
        fprintf(pfile,"%9dB ",rgSizes[i]);
    fprintf(pfile,"\n");
    for (i=0; i<cRates; i++) {
        fprintf(pfile,"%7dBps ",rgRates[i]);
        for (j=0; j<cSizes; j++) {
            k = IndexOfStatRecWith(rgRates[i],rgSizes[j],-1,pStats,cStaFiles);
            if (k != -1) {
                fprintf(pfile,"%10.2lf ",pStats[k].mean);
            }
            else {
                fprintf(pfile,"           ");
            }
        }
        fprintf(pfile,"\n");
    }
    fprintf(pfile,"\n");

    // variance
    fprintf(pfile,"Latency Standard Deviation\n");
    fprintf(pfile,"           ");
    for (i=0; i<cSizes; i++)
        fprintf(pfile,"%9dB ",rgSizes[i]);
    fprintf(pfile,"\n");
    for (i=0; i<cRates; i++) {
        fprintf(pfile,"%7dBps ",rgRates[i]);
        for (j=0; j<cSizes; j++) {
            k = IndexOfStatRecWith(rgRates[i],rgSizes[j],-1,pStats,cStaFiles);
            if (k != -1) {
                fprintf(pfile,"%10.2lf ",sqrt((double)pStats[k].var));
            }
            else {
                fprintf(pfile,"           ");
            }
        }
        fprintf(pfile,"\n");
    }
    fprintf(pfile,"\n");

    // skew
    fprintf(pfile,"Latency Skew\n");
    fprintf(pfile,"           ");
    for (i=0; i<cSizes; i++)
        fprintf(pfile,"%9dB ",rgSizes[i]);
    fprintf(pfile,"\n");
    for (i=0; i<cRates; i++) {
        fprintf(pfile,"%7dBps ",rgRates[i]);
        for (j=0; j<cSizes; j++) {
            k = IndexOfStatRecWith(rgRates[i],rgSizes[j],-1,pStats,cStaFiles);
            if (k != -1) {
                fprintf(pfile,"%10.2lf ",pStats[k].skew);
            }
            else {
                fprintf(pfile,"           ");
            }
        }
        fprintf(pfile,"\n");
    }
    fprintf(pfile,"\n");

    // kurtosis
    fprintf(pfile,"Latency Kurtosis\n");
    fprintf(pfile,"           ");
    for (i=0; i<cSizes; i++)
        fprintf(pfile,"%9dB ",rgSizes[i]);
    fprintf(pfile,"\n");
    for (i=0; i<cRates; i++) {
        fprintf(pfile,"%7dBps ",rgRates[i]);
        for (j=0; j<cSizes; j++) {
            k = IndexOfStatRecWith(rgRates[i],rgSizes[j],-1,pStats,cStaFiles);
            if (k != -1) {
                fprintf(pfile,"%10.2lf ",pStats[k].kurt);
            }
            else {
                fprintf(pfile,"           ");
            }
        }
        fprintf(pfile,"\n");
    }
    fprintf(pfile,"\n");

    // send rate
    fprintf(pfile,"Send Rate (Bps)\n");
    fprintf(pfile,"           ");
    for (i=0; i<cSizes; i++)
        fprintf(pfile,"%9dB ",rgSizes[i]);
    fprintf(pfile,"\n");
    for (i=0; i<cRates; i++) {
        fprintf(pfile,"%7dBps ",rgRates[i]);
        for (j=0; j<cSizes; j++) {
            k = IndexOfStatRecWith(rgRates[i],rgSizes[j],-1,pStats,cStaFiles);
            if (k != -1) {
                fprintf(pfile,"%10.1lf ",pStats[k].sendrate);
            }
            else {
                fprintf(pfile,"           ");
            }
        }
        fprintf(pfile,"\n");
    }
    fprintf(pfile,"\n");

    // recv rate
    fprintf(pfile,"Receive Rate (Bps)\n");
    fprintf(pfile,"           ");
    for (i=0; i<cSizes; i++)
        fprintf(pfile,"%9dB ",rgSizes[i]);
    fprintf(pfile,"\n");
    for (i=0; i<cRates; i++) {
        fprintf(pfile,"%7dBps ",rgRates[i]);
        for (j=0; j<cSizes; j++) {
            k = IndexOfStatRecWith(rgRates[i],rgSizes[j],-1,pStats,cStaFiles);
            if (k != -1) {
                fprintf(pfile,"%10.1lf ",pStats[k].recvrate);
            }
            else {
                fprintf(pfile,"           ");
            }
        }
        fprintf(pfile,"\n");
    }
    fprintf(pfile,"\n");

    // show the file to the screen, just for kicks
    rewind(pfile);
    while (fgets(szLineBuf, 1000, pfile) != NULL)
        printf("%s", szLineBuf);
        
    // we're done, so we free up the memory we used    
    printf("Saved aggregate stats to %s\n",szAggFile);
    fclose(pfile);
    for (i=0; i<cStaFiles; i++) {
        free(rgszStaFiles[i]);
    }
    free(pStats);
}

int IndexOfStatRecWith(int rate, int size, INT64 time, PSTATS pStats, int cStats) {
    // returns an index into pStats that has the requested values for rate and size
    // if there are more than one, returns arbitrary match
    // returns -1 if no suitable entry is found.
    int i;
    ULARGE_INTEGER uliT;

    for (i=0; i<cStats; i++) {
        if (rate == -1 || pStats[i].nTokenRate == rate) {
            if (size == -1 || pStats[i].nBytesPerBuffer == size) {
                CopyMemory(&uliT, &(pStats[i].time), sizeof(ULARGE_INTEGER));
                if (time == -1 || uliT.QuadPart == (UINT64)time) {
                    return i;
                }
            }
        }
    }

    return -1;
}

BOOL GetStatsFromFile(PSTATS pstats) {
    // this function gets the overall statistics from the .sta file it's pointed to
    // it returns true if successful, false otherwise
    PCHAR szBuf = NULL;
    double T1,T2,T3;
    int nT1,nT2,nT3,nT4,nT5,nT6;
    HANDLE hFile;
    DWORD dwFileSize;
    DWORD dwRead;
    int nFields;
    SYSTEMTIME st;

    szBuf = malloc(sizeof(CHAR) * 1000);
    
    if (!szBuf) return FALSE;
        
    ZeroMemory(szBuf,1000);
    // open the file
    hFile = CreateFile(pstats->szStaFile,GENERIC_READ, FILE_SHARE_READ, NULL, 
                       OPEN_EXISTING, 0, NULL);
    dwFileSize = GetFileSize(hFile, NULL);
    if (dwFileSize == 0) return FALSE;
    
    // read the whole file into szBuf
    ReadFile(hFile, szBuf, dwFileSize, &dwRead, NULL);

    // close the file
    CloseHandle(hFile);

    // parse the buffer
    nFields = sscanf(szBuf,
                 "Sender: %s Receiver: %s\n" \
                 "First packet received: %hu:%hu.%hu.%hu %hu/%hu/%hu (UTC)\n" \
                 "Buffer size: %d\tTokenrate: %d\n" \
                 "Received %d packets.\n" \
                 "Logged %d records.\n" \
                 "Received %d bytes in %d milliseconds = %d KBps\n" \
                 "Clock skew is %lf microseconds per second.\n " \
                 "\tbased on %d calibration points\n" \
                 "Overall send rate: %lf Bytes/s\n" \
                 "Overall recv rate: %lf Bytes/s\n" \
                 "Latency Statistics (microsecond units): median: %lf\n" \
                 "\tMean: %lf\tStdev: %lf\tAbDev: %lf\n" \
                 "\tVariance: %lf\tSkew: %lf\t Kurtosis: %lf \n" \
                 "Dropped %d packets\n",
                 pstats->szSender, pstats->szReceiver,
                 &(st.wHour), &(st.wMinute), &(st.wSecond), &(st.wMilliseconds), 
                 &(st.wDay), &(st.wMonth), &(st.wYear),
                 &(pstats->nBytesPerBuffer), &(pstats->nTokenRate),
                 &(pstats->nBuffers), &nT2, &nT3, &nT4, &nT5, &T1, &nT6, &(pstats->sendrate), 
                 &(pstats->recvrate), &(pstats->median),
                 &(pstats->mean),&T2,&(pstats->abdev),
                 &(pstats->var),&(pstats->skew),&(pstats->kurt),
                 &(pstats->nDrops));

    if (nFields != 28 && nFields != 27) { // see if they ran without clock skew calc
        nFields = sscanf(szBuf,
                 "Sender: %s Receiver: %s\n" \
                 "First packet received: %hu:%hu.%hu.%hu %hu/%hu/%hu (UTC)\n" \
                 "Buffer size: %d\tTokenrate: %d\n" \
                 "Received %d packets.\n" \
                 "Logged %d records.\n" \
                 "Received %d bytes in %d milliseconds = %d KBps\n" \
                 "Overall send rate: %lf Bytes/s\n" \
                 "Overall recv rate: %lf Bytes/s\n" \
                 "Latency Statistics (microsecond units): median: %lf\n" \
                 "\tMean: %lf\tStdev: %lf\tAbDev: %lf\n" \
                 "\tVariance: %lf\tSkew: %lf\t Kurtosis: %lf \n" \
                 "Dropped %d packets\n",
                 pstats->szSender, pstats->szReceiver,
                 &(st.wHour), &(st.wMinute), &(st.wSecond), &(st.wMilliseconds), 
                 &(st.wDay), &(st.wMonth), &(st.wYear),
                 &(pstats->nBytesPerBuffer), &(pstats->nTokenRate),
                 &nT1, &nT2, &nT3, &nT4, &nT5, &(pstats->sendrate), 
                 &(pstats->recvrate), &(pstats->median),
                 &(pstats->mean),&T2,&(pstats->abdev),
                 &(pstats->var),&(pstats->skew),&(pstats->kurt),
                 &(pstats->nDrops));
    
        if (nFields != 26 && nFields != 25) return FALSE;
    }


    // assemble a FILETIME structure from the date & time
    if (!SystemTimeToFileTime(&st,&pstats->time)) {
        return FALSE;
    }

    free(szBuf);

    return TRUE;    
}

VOID
DoStatsFromFile()
{
    DOUBLE slope = 0;
	DOUBLE offset = 0;
	
    printf("Logging stats from file.\n");
	if (Name == NULL) {
		fprintf(stderr, "ERROR: you must specify a file to convert\n");
	}
	if(MyCreateFile(Name, ".log", &hLogFile) != ERROR_SUCCESS) {
		fprintf(stderr, "ERROR: could not create log file\n");
		exit(1);
	}
	if(OpenRawFile(Name, &hRawFile) != ERROR_SUCCESS) {
		fprintf(stderr, "ERROR: could not open raw file\n");
		exit(1);
	}

    ReadSchedulingRecords(hRawFile);
		
    if (g_params.calibration == 0)
        g_params.calibration = g_log.nBuffersLogged;
        
	NormalizeTimeStamps();

    // here we check for wacky timestamps on the sender and receiver
    if (g_params.SkewFitMode == 3)
        FixWackyTimestamps();
    
	if (g_params.SkewFitMode) {
    	ClockSkew(&slope, &offset);
	    AdjustForClockSkew(slope,offset);
	    NormalizeTimeStamps();
	}

	if(hLogFile != INVALID_HANDLE_VALUE) {
		WriteSchedulingRecords(hLogFile, g_params.Dummy);
	}
	printf("Done stats from file.\n");
} // DoStatsFromFile()

DWORD
OpenRawFile(
	IN PCHAR Name,
	OUT HANDLE *File
	)
{   
	HANDLE hFile;
    UCHAR * logName;

    logName = malloc(strlen(Name) + 4);
    strncpy(logName, Name, strlen(Name));
    
    logName[strlen(Name)+0] = '.';
    logName[strlen(Name)+1] = 'r';
    logName[strlen(Name)+2] = 'a';
    logName[strlen(Name)+3] = 'w';
    logName[strlen(Name)+4] = (UCHAR)NULL;

    hFile = CreateFile(logName,
                       GENERIC_READ,
                       0,
                       NULL,
                       OPEN_EXISTING ,
                       FILE_ATTRIBUTE_NORMAL,
                       NULL);
                       
    *File = hFile;

    return(INVALID_HANDLE_VALUE == hFile ? (!(ERROR_SUCCESS)) : ERROR_SUCCESS);
} // OpenRawFile()

INT64 ReadSchedulingRecords(HANDLE File)
{
    char szTempFile[MAX_PATH];
    char szTempPath[MAX_PATH];
	LOG_RECORD currentRecord;
	CHAR lineBuf[MAX_STRING];
	CHAR nextChar[2] = {0,0};
	DWORD readBytes = 0;
	INT assignedFields;

	if (!File || (File == INVALID_HANDLE_VALUE)) {
		fprintf(stderr,"ERROR: Invalid File\n");
		return 0;
	}

    CreateLog(&g_log, 2048);
	// loop through the file, reading in line after line
	do 
	{
		// get the next line of characters
		bzero(lineBuf, MAX_STRING);
		ZeroMemory(lineBuf, MAX_STRING);
		do {
			ReadFile(File,nextChar,1,&readBytes,NULL);
			if (readBytes == 0) {
			    if (g_log.nBuffersLogged == 0) {
			        fprintf(stderr,"ERROR: no logs read\n");
			        exit(1);
			    }
			    break;
			}
			strcat(lineBuf,nextChar);
		} while (*nextChar != '\n');
		// parse line and add it to the log
		assignedFields = sscanf(lineBuf, 
			"%I64u:%I64u:%I64u:%d:%d\n", 
			&(currentRecord.TimeSent),
			&(currentRecord.TimeReceived),
			&(currentRecord.Latency),
			&(currentRecord.BufferSize),
			&(currentRecord.SequenceNumber));
		if ((assignedFields != 5) && (assignedFields != EOF))
			printf("ERROR: parsing the log gave bad field assignments on record %d\n", 
			    g_log.nBuffersLogged);

		if (assignedFields == EOF) break;
        AddLogEntry(&g_log, &currentRecord);
	}
	while (readBytes != 0);

    printf("read %d records\n",g_log.nBuffersLogged);
	return g_log.nBuffersLogged;  // return the number of records read
} // ReadSchedulingRecords()

VOID
DoStats()
{
    DOUBLE slope = 0;
    DOUBLE offset = 0;

    GenericStats();
    
    if(!normalize){
        if(hRawFile != INVALID_HANDLE_VALUE){
            WriteSchedulingRecords(hRawFile, FALSE);
        }
    }
    
    NormalizeTimeStamps();
    
    if(normalize){
        if(hRawFile != INVALID_HANDLE_VALUE){
            WriteSchedulingRecords(hRawFile, FALSE);
        }
    }

    if(!g_params.calibration) { // if we have nothing specified, calibrate on all buffers
        g_params.calibration = g_state.nBuffersReceived;
    }
   
    // here we check for wacky timestamps on the sender and receiver
    if (g_params.SkewFitMode == 3)
        FixWackyTimestamps();
    
    if(g_params.SkewFitMode) {
        ClockSkew(&slope, &offset);
        AdjustForClockSkew(slope, offset);
        NormalizeTimeStamps();
    }

    // we calculate these stats on the normalized / skew adjusted data
    AdvancedStats();
    
    CheckForLostPackets();
    
    if(hLogFile != INVALID_HANDLE_VALUE){
        WriteSchedulingRecords(hLogFile, g_params.Dummy);
    }
    printf("\n");
}

VOID
WriteSchedulingRecords(
    HANDLE File,
    BOOLEAN InsertDummyRows)
{
    
    LOG_RECORD scheduleRecord;
    CHAR formattingBuffer[MAX_STRING];
    INT dwWritten;
    INT64 records = g_log.nBuffersLogged;
	INT wrote;
    INT i;
    INT64 maxLatency = (INT64)0;

    if(!File || (File == INVALID_HANDLE_VALUE)){
        return;
    }

    while(records){
        GetLogEntry(&g_log, &scheduleRecord, g_log.nBuffersLogged - records);
		ZeroMemory(formattingBuffer,MAX_STRING);
		
        wrote = sprintf(formattingBuffer,
		                "%020I64u:%020I64u:%010I64d:%10d:%10d\n", 
						scheduleRecord.TimeSent,
						scheduleRecord.TimeReceived,
				        scheduleRecord.Latency,
					    scheduleRecord.BufferSize,
						scheduleRecord.SequenceNumber);

        WriteFile(File, formattingBuffer, wrote, &dwWritten, NULL);

        records--;
    }
} // WriteSchedulingRecords()

VOID
GenericStats()
{
    INT bytesWritten;
    UCHAR holdingBuffer[MAX_STRING];
    INT count;

    // say who the sender and receiver are
    count = sprintf(holdingBuffer, "Sender: %s Receiver: %s\n",szHisAddr, szMyAddr);
    WriteStats(holdingBuffer, count);
    printf("%s",holdingBuffer);

    // say when we received the first packet
    count = sprintf(holdingBuffer, "First packet received: %02u:%02u.%02u.%03u %02u/%02u/%04u (UTC)\n",
        systimeStart.wHour, systimeStart.wMinute, systimeStart.wSecond, 
        systimeStart.wMilliseconds, systimeStart.wDay, systimeStart.wMonth, systimeStart.wYear);
    WriteStats(holdingBuffer, count);
    printf("%s",holdingBuffer);
    
    // write the test params to the .sta file
    bzero(holdingBuffer, MAX_STRING);
    count = _snprintf(holdingBuffer, MAX_STRING -1,
                      "Buffer size: %d\tTokenrate: %d\n",
                      g_params.buflen, g_params.TokenRate);
    WriteStats(holdingBuffer, count);
    printf("%s",holdingBuffer);
    
    // write some generic results
    bzero(holdingBuffer, MAX_STRING);
    count = _snprintf(holdingBuffer,
                      MAX_STRING-1, // leave room for NULL
                      "Received %u packets.\n",
                      g_state.nBuffersReceived);
    WriteStats(holdingBuffer, count);
    printf("%s",holdingBuffer);

    bzero(holdingBuffer, MAX_STRING);
    count = _snprintf(holdingBuffer,
                      MAX_STRING-1, // leave room for NULL
                      "Logged %I64u records.\n",
                      g_log.nBuffersLogged);
    WriteStats(holdingBuffer, count);
    printf("%s",holdingBuffer);

    bzero(holdingBuffer, MAX_STRING);
    count = _snprintf(holdingBuffer,
                      MAX_STRING-1, // room for NULL
                      "Received %ld bytes in %I64d milliseconds = %I64d KBps\n",
                      g_state.nBytesTransferred,
                      timeElapsed,
                      g_state.nBytesTransferred/timeElapsed);
    WriteStats(holdingBuffer, count);
    printf("%s",holdingBuffer);
} // GenericStats()

void AdvancedStats() {
    // write some more interesting stats to the .sta file
    char szBuf[MAX_STRING];
    INT64 i,n;
    int count;
    INT64 FirstTime,LastTime;
    double rate, median, mean, var, abdev, skew, kurt, sdev, ep = 0.0, s, p;
    LOG_RECORD rec;
    double * sortedLatencies;

    // overall send rate
    GetLogEntry(&g_log, &rec, 0);
    FirstTime = rec.TimeSent;
    GetLogEntry(&g_log, &rec, g_log.nBuffersLogged - 1);
    LastTime = rec.TimeSent;
    rate = (rec.SequenceNumber * g_params.buflen)/((double)(LastTime - FirstTime)/10000000.0);
    count = sprintf(szBuf, "Overall send rate: %.3f Bytes/s\n",rate);
    WriteStats(szBuf, count);
    printf("%s",szBuf);
    GetLogEntry(&g_log, &rec, 0);
    FirstTime = rec.TimeReceived;
    GetLogEntry(&g_log, &rec, g_log.nBuffersLogged - 1);
    LastTime = rec.TimeReceived;
    rate = (g_state.nBytesTransferred)/((double)(LastTime - FirstTime)/10000000.0);
    count = sprintf(szBuf, "Overall recv rate: %.3f Bytes/s\n",rate);
    WriteStats(szBuf, count);
    printf("%s",szBuf);

    // now show mean, variance, avdev, etc of latency.
    s = 0.0;
    n = g_log.nBuffersLogged;
    sortedLatencies = malloc(sizeof(double) * (UINT)n);
    for (i=0; i < n; i++) { // first pass, we get mean
        GetLogEntry(&g_log, &rec, i);
        s += (double)rec.Latency/10.0;
        sortedLatencies[i] = (double)rec.Latency/10.0;
    }
    qsort(sortedLatencies,(UINT)n,sizeof(double),compare);
    median = (n & 1) ? sortedLatencies[(n-1)/2] : 0.5*(sortedLatencies[n/2] + sortedLatencies[n/2 - 1]);
    free(sortedLatencies);
    mean = s / n;
    abdev = var = skew = kurt = 0.0;
    for (i=0; i<n; i++) { // second pass, we get 1st,2nd,3rd,4th moments of deviation from mean
        GetLogEntry(&g_log, &rec, i);
        abdev += fabs(s=(double)rec.Latency/10.0 - mean);
        ep += s;
        var += (p = s*s);
        skew += (p *= s);
        kurt += (p *= s);
    }
    abdev /= n;
    var = (var - ep*ep/n) / (n-1);
    sdev = sqrt(var);
    if (var) {           // if var=0, no skew/kurtosis defined
        skew /= (n*var*sdev);
        kurt  = kurt / (n*var*var) - 3.0;
    }

    count = sprintf(szBuf, "Latency Statistics (microsecond units): median: %.1lf\n",median);
    WriteStats(szBuf, count);
    printf("%s",szBuf);
    count = sprintf(szBuf, "\tMean:     %6.2lf\tStdev: %6.2lf\tAbDev:    %6.2lf\n",mean,sdev,abdev);
    WriteStats(szBuf, count);
    printf("%s",szBuf);
    count = sprintf(szBuf, "\tVariance: %6.2lf\tSkew:  %6.2lf\tKurtosis: %6.2lf\n",var,skew,kurt);
    WriteStats(szBuf, count);
    printf("%s",szBuf);
}

VOID
CheckForLostPackets()
{
    LOG_RECORD currentRecord;
    INT currentSequenceNumber = 0;
    INT bytesWritten;
    UCHAR holdingBuffer[MAX_STRING];
    INT count;
    INT64 nLost = 0;
    INT i;

    for(i=0; i<g_log.nBuffersLogged; i++){
        GetLogEntry(&g_log, &currentRecord, i);
        if(currentRecord.SequenceNumber != currentSequenceNumber){
            nLost += currentRecord.SequenceNumber - currentSequenceNumber;
            currentSequenceNumber = currentRecord.SequenceNumber;
        }

        currentSequenceNumber += g_params.LoggingPeriod;
    }
    count = sprintf(holdingBuffer, "Dropped %I64u packets\n", nLost);
    WriteStats(holdingBuffer, count);
} // CheckForLostPackets()

VOID
WriteStats(
    UCHAR * HoldingBuffer,
    INT Count)
{
    INT bytesWritten;

    if(Count < 0){
        Count = MAX_STRING;
    }

    WriteFile(hStatFile,
              HoldingBuffer,
              Count,
              &bytesWritten,
              NULL);
} // WriteStats()

VOID
NormalizeTimeStamps()
{
    LOG_RECORD currentRecord;
    INT bytesWritten;
    UCHAR holdingBuffer[MAX_STRING];
    INT count;
    INT i;

    UINT64 timeSent;
    UINT64 timeReceived;
    UINT64 smaller;
    INT64 constantDelay = MAX_INT64;
    INT64 currentDelay;
    UINT64 base = 0xFFFFFFFFFFFFFFFF;

    for(i=0; i<g_log.nBuffersLogged; i++){
        GetLogEntry(&g_log, &currentRecord, i);
        currentDelay = currentRecord.TimeReceived - currentRecord.TimeSent;
        constantDelay = (currentDelay < constantDelay) ? currentDelay : constantDelay;
    }

    // now subtract off the constant delay off
    for(i=0; i<g_log.nBuffersLogged; i++){
        GetLogEntry(&g_log, &currentRecord, i);
        currentRecord.TimeReceived -= constantDelay;
        currentRecord.Latency = currentRecord.TimeReceived - currentRecord.TimeSent;
        SetLogEntry(&g_log, &currentRecord, i);
    }

    for (i=0; i<g_log.nBuffersLogged; i++) {
        GetLogEntry(&g_log, &currentRecord, i);
        smaller = (currentRecord.TimeReceived < currentRecord.TimeSent) ?
            currentRecord.TimeReceived : currentRecord.TimeSent;
        base = (base < smaller)?base:smaller;  // find the smallest timestamp
    }        
    
    // now we can subtract the base off of the send & receive times
    for (i=0; i<g_log.nBuffersLogged; i++) {
        GetLogEntry(&g_log, &currentRecord, i);
        currentRecord.TimeSent -= base;
        currentRecord.TimeReceived -= base;
        SetLogEntry(&g_log, &currentRecord, i);
    }
} // NormalizeTimeStamps()

VOID
ClockSkew(
    DOUBLE * Slope,
    DOUBLE * Offset) {
    // If there is a calibration period, we can estimate clock skew between
    // sender and receiver. See comments under AdjustForClockSkew. We use
    // calculus to determine the best-fit slope.

    INT i;
    LOG_RECORD currentRecord;
	DOUBLE N;
    DOUBLE slope;
    DOUBLE offset;
    UCHAR holdingBuffer[MAX_STRING];
    INT count;
    double *x, *y, abdev;
    double devpercent;
    
    // We find the clock skew using medfit, a function which fits to minimum absolute deviation
    N = (double) g_params.calibration;
    x = malloc(sizeof(double) * (UINT)N);
    y = malloc(sizeof(double) * (UINT)N);
    for (i = 0; i<N; i++) {
        GetLogEntry(&g_log,&currentRecord,i);
        x[i] = (DOUBLE)currentRecord.TimeSent;
        y[i] = (DOUBLE)currentRecord.Latency;
    }
    medfit(x, y, (INT)N, &offset, &slope, &abdev);

    // Now write out our findings.
    bzero(holdingBuffer, MAX_STRING);

    count = _snprintf(holdingBuffer,
					MAX_STRING-1, // leave room for NULL
					"Clock skew is %f microseconds per second.\n  " \
					"\tbased on %d calibration points\n",
					100000*slope, g_params.calibration);

    WriteStats(holdingBuffer, count);
	printf("%s",holdingBuffer);

    for (i = 0,devpercent = 0.0; i<N; i++) {
        devpercent += y[i];
    }
    devpercent /= N;
    devpercent = 100 * abdev / devpercent;

    printf("\tfit resulted in avg. absolute deviation of %f percent from mean\n",devpercent);

    free(x);
    free(y);
    *Slope = slope;
    *Offset = offset;
} // ClockSkew()

BOOLEAN
AnomalousPoint(
			   DOUBLE x,
			   DOUBLE y)
{
	// here we simply keep a buffer of the past 10 calls and if this one 
	// falls out of a few standard deviations of the 8 inner points, we deem it anomalous
	static DOUBLE buf[10];
	DOUBLE sortedbuf[10];
	DOUBLE mean = 0;
	DOUBLE sum = 0;
	DOUBLE sumsqdev = 0;
	DOUBLE median = 0;
	DOUBLE sdev = 0;
	DOUBLE N;
	static int curIndex = 0;
	int i;
	static INT64 submittedPoints;

	buf[curIndex % 10] = y;
	curIndex++;
	submittedPoints++;
	
	if (g_params.SkewFitMode != 4)
		return FALSE;

	if (submittedPoints >= 10) {
		sum = 0;
		sumsqdev = 0;

        // sort them into sortedbuf
        for (i=0; i<10; i++) sortedbuf[i] = buf[i];
        qsort(sortedbuf, 10, sizeof(DOUBLE), compare);

        // use only the inner 8 points in the calculation of mean & var
		for (i = 1; i < 9; i++) {
			sum += sortedbuf[i];
		}

		N = 8.0; // using only 8 points
		mean = sum / N;

		for (i = 1; i < 9; i++) {
			sumsqdev += ((sortedbuf[i] - mean) * (sortedbuf[i] - mean));
		}
		
		sdev = sqrt(sumsqdev / N);
		if (fabs(y - mean) < 2.5 * sdev) {
			return FALSE;
		}
		else {
		    anomalies++;
			return TRUE;
		}
	}

	return TRUE;
} // AnomalousPoint()

VOID
AdjustForClockSkew(
    DOUBLE Slope,
    DOUBLE Offset)
{
    //
    // When measuring very low jitter, clock drift between machines 
    // introduces noise in the form of a monotonically increasing 
    // skew between sending and receiving clock. This effect can be 
    // filtered out by finding the best-fit slope for all samples 
    // taken during the calibration period, then using this slope to
    // normalize the entire run. This routine normalizes using the 
    // slope determined in the routine ClockSkew.
    //

    INT i;
    LOG_RECORD currentRecord;
    INT64 minLatency = MAX_INT64;
    INT64 x;
    DOUBLE mXPlusB;

    for(i=0; i < g_log.nBuffersLogged; i++){
        GetLogEntry(&g_log, &currentRecord, i);
        mXPlusB = (currentRecord.TimeSent*Slope) + Offset; // offset is not necessary

        currentRecord.TimeReceived -= (INT64)mXPlusB;
        currentRecord.Latency -= (INT64)mXPlusB;

        SetLogEntry(&g_log, &currentRecord, i);

        //
        // find the minimum latency value
        //

        minLatency = (currentRecord.Latency < minLatency)?
                        currentRecord.Latency:
                        minLatency;
    }

    for(i=0; i < g_log.nBuffersLogged; i++){
        GetLogEntry(&g_log, &currentRecord, i);
        currentRecord.Latency -= minLatency;
        currentRecord.TimeReceived -= minLatency;
        SetLogEntry(&g_log, &currentRecord, i);   
    }
} // AdjustForClockSkew()

#define WACKY 2.5

BOOL FixWackyTimestamps() {
    // This routine will look over the sender & receiver timestamps and try to see if there
    // are any non-clock skew related irregularities (such as one of them bumping it's clock
    // a fixed amount every once-in-a-while) and try to remove them.
    INT64 *sendstamps, *recvstamps;
    double *sendgaps, *recvgaps;
    double *sortedsendgaps, *sortedrecvgaps;
    double sendmean, sendsdev, sendsum, sendsumsqdev;
    double recvmean, recvsdev, recvsum, recvsumsqdev;
    double mediansendgap, medianrecvgap;
    double modesendgap, moderecvgap;
    double meansendwackiness, sdevsendwackiness, sumsendwackiness, sumsqdevsendwackiness;
    double meanrecvwackiness, sdevrecvwackiness, sumrecvwackiness, sumsqdevrecvwackiness;
    double fractionaldevofsendwackiness, fractionaldevofrecvwackiness;
    double normalsendgapmean, normalrecvgapmean;
    double trimmeansendgap, trimmeanrecvgap;
    BOOL *fWackoSend, *fWackoRecv;
    int cWackoSend, cWackoRecv;
    BOOL *fMaybeWackoSend, *fMaybeWackoRecv;
    int i,N;
    LOG_RECORD currentRecord;
    const double FixThreshold = 0.1;
    double CumulativeFixMagnitude = 0.0;

    N = (int)g_log.nBuffersLogged;
    cWackoSend = cWackoRecv = 0;
    // fill our arrays.
    sendstamps = malloc(sizeof(INT64) * N);
    recvstamps = malloc(sizeof(INT64) * N);
    sendgaps = malloc(sizeof(double) * N);
    recvgaps = malloc(sizeof(double) * N);
    sortedsendgaps = malloc(sizeof(double) *N);
    sortedrecvgaps = malloc(sizeof(double) *N);
    fWackoRecv = malloc(sizeof(BOOL) * N);
    fWackoSend = malloc(sizeof(BOOL) * N);
    fMaybeWackoSend = malloc(sizeof(BOOL) * N);
    fMaybeWackoRecv = malloc(sizeof(BOOL) * N);

    for (i=0; i<N; i++) {
        GetLogEntry(&g_log, &currentRecord, i);
        sendstamps[i] = currentRecord.TimeSent;
        recvstamps[i] = currentRecord.TimeReceived;
        fWackoSend[i] = FALSE;
        fMaybeWackoSend[i] = FALSE;
        fWackoRecv[i] = FALSE;
        fMaybeWackoRecv[i] = FALSE;
    }
    
    // First, check for wacky timestamps. This is a multistep process:
    //    1. Calculate the interpacket gaps on both sender & receiver.
    for (i=1; i<N; i++) {
        sendgaps[i] = (double) (sendstamps[i] - sendstamps[i-1]);
        recvgaps[i] = (double) (recvstamps[i] - recvstamps[i-1]);
    }
    //    2. We will define wacky as being at least WACKY standard deviations away from the
    //       mean.
    sendsum = recvsum = 0.0;
    for (i=1; i<N; i++) {
        sendsum += sendgaps[i];
        recvsum += recvgaps[i];
    }
    sendmean = sendsum / N;
    recvmean = recvsum / N;
    sendsumsqdev = recvsumsqdev = 0.0;
    for (i=1; i<N; i++) {
        sendsumsqdev += ((sendgaps[i] - sendmean) * (sendgaps[i] - sendmean));
        recvsumsqdev += ((recvgaps[i] - recvmean) * (recvgaps[i] - recvmean));
    }
	sendsdev = sqrt(sendsumsqdev / N);
	recvsdev = sqrt(recvsumsqdev / N);

    for (i=1; i<N; i++) {
        if ((sendgaps[i] < sendmean - WACKY*sendsdev) ||
            (sendgaps[i] > sendmean + WACKY*sendsdev)) {
            fMaybeWackoSend[i] = fWackoSend[i] = TRUE;
        }
        if ((recvgaps[i] < recvmean - WACKY*recvsdev) ||
            (recvgaps[i] > recvmean + WACKY*recvsdev)) {
            fMaybeWackoRecv[i] = fWackoRecv[i] = TRUE;
        }        
    }
    
    //    3. Check to see if any wacky points are unpaired (that is, a wacky point in the
    //       sending timestamps is not matched with an equally wacky point in the receiving
    //       timestamps).
    for (i=1; i<N; i++) {
        if (fMaybeWackoSend[i] && fMaybeWackoRecv[i]) {
            // I should check to make sure they're equally wacky, but i'm not currently
            fMaybeWackoSend[i] = fWackoSend[i] = FALSE;
            fMaybeWackoRecv[i] = fWackoRecv[i] = FALSE;
        }
    }
    //    4. Check to see if any wacky unpaired points are solitary (that is, they are not
    //       surrounded by other wacky points).
    for (i=1; i<N-1; i++) {
        if (fMaybeWackoSend[i]) {
            if (fMaybeWackoSend[i-1] || fMaybeWackoSend[i+1]) {
                fWackoSend[i] = FALSE;
            }
        }
        if (fMaybeWackoRecv[i]) {
            if (fMaybeWackoRecv[i-1] || fMaybeWackoRecv[i+1]) {
                fWackoRecv[i] = FALSE;
            }
        }
    }
    if (fMaybeWackoSend[N-1] && fMaybeWackoSend[N-2]) fWackoSend[N-1] = FALSE;
    if (fMaybeWackoRecv[N-1] && fMaybeWackoRecv[N-2]) fWackoRecv[N-1] = FALSE;
    //    5. If we find a point that meets all these criteria, label it wacky and add it to
    //       our list of wacky points.
    for (i=1; i<N; i++) {
        fMaybeWackoSend[i] = fWackoSend[i];
        fMaybeWackoRecv[i] = fWackoRecv[i];    
    }

    // Now we find out the stats for the sends & receivees to use as the baseline
    sendsum = recvsum = 0.0;
    cWackoSend = cWackoRecv = 0;
    for (i=1; i<N; i++) {
        sortedsendgaps[i] = sendgaps[i];
        sortedrecvgaps[i] = recvgaps[i];
        if (!fWackoSend[i]) {
            sendsum += sendgaps[i];
            cWackoSend++;
        }
        if (!fWackoRecv[i]) {
            recvsum += recvgaps[i];
            cWackoRecv++;
        }
    }
    normalsendgapmean = sendsum / cWackoSend;
    normalrecvgapmean = recvsum / cWackoRecv;
    qsort(sortedsendgaps, N, sizeof(double), compare);
    qsort(sortedrecvgaps, N, sizeof(double), compare);
    if (N & 1) { // odd N
        mediansendgap = sortedsendgaps[(N+1) / 2];
        medianrecvgap = sortedrecvgaps[(N+1) / 2];
    } else { // even N
        i = N/2;
        mediansendgap = 0.5 * (sortedsendgaps[i] + sortedsendgaps[i+1]);
        medianrecvgap = 0.5 * (sortedrecvgaps[i] + sortedrecvgaps[i+1]);
    }
    sendsum = recvsum = 0.0;
    for (i=(int)(0.05*N); i<(int)(0.85*N); i++) { // find the 80% trimmean (bottom heavy)
        sendsum += sortedsendgaps[i];
        recvsum += sortedrecvgaps[i];
    }
    trimmeansendgap = sendsum / (0.80 * N);
    trimmeanrecvgap = recvsum / (0.80 * N);
    modesendgap = mode(sendgaps, N);
    moderecvgap = mode(recvgaps, N);

    // 6. we have to check to see if the wackiness at each wacky point is about equal to what
    // we think it ought to be, based on the timer clock
    for (i=1; i<N; i++) {
        if (fWackoSend[i]) {
            if (!InRange(sendgaps[i] - g_BadHalAdjustment, 
                    mediansendgap - sendsdev, mediansendgap + sendsdev)) {
               fWackoSend[i] = FALSE;
               cWackoSend--;
            }
        }
        if (fWackoRecv[i]) {
            if (!InRange(recvgaps[i] - g_BadHalAdjustment, 
                    medianrecvgap - recvsdev, medianrecvgap + recvsdev)) {
               fWackoRecv[i] = FALSE;
               cWackoRecv--;
            }
        }
    }

    // Now we want to correct for the wacky timestamps, so we see if the wacky points are all
    // equally wacky. If they are, we're psyched and we simply subtract off the wackiness
    // from the wacky points and all points after them. (Wackiness is cumulative!)
    cWackoSend = cWackoRecv = 0;
    sumsendwackiness = sumrecvwackiness = sumsqdevsendwackiness = sumsqdevrecvwackiness = 0.0;
    for (i=1; i<N; i++) {
        if (fWackoSend[i]) {
            sumsendwackiness += (sendgaps[i] - trimmeansendgap);
            cWackoSend++;
        }
        if (fWackoRecv[i]) {
            sumrecvwackiness += (recvgaps[i] - trimmeanrecvgap);
            cWackoRecv++;
        }
    }
    meansendwackiness = sumsendwackiness / cWackoSend;
    meanrecvwackiness = sumrecvwackiness / cWackoRecv;
    for (i=1; i<N; i++) {
        if (fWackoSend[i])
            sumsqdevsendwackiness += ((sendgaps[i]-trimmeansendgap-meansendwackiness) * (sendgaps[i]-normalsendgapmean-meansendwackiness));
        if (fWackoRecv[i])
            sumsqdevrecvwackiness += ((recvgaps[i]-trimmeanrecvgap-meanrecvwackiness) * (recvgaps[i]-normalrecvgapmean-meanrecvwackiness));
    }
    sdevsendwackiness = sqrt(sumsqdevsendwackiness / cWackoSend);
    sdevrecvwackiness = sqrt(sumsqdevrecvwackiness / cWackoRecv);
    
    // so if the fractional deviation is less than some set amount, we apply the fix
    fractionaldevofsendwackiness = sdevsendwackiness / meansendwackiness;
    fractionaldevofrecvwackiness = sdevrecvwackiness / meanrecvwackiness;
    if (cWackoSend && (fractionaldevofsendwackiness < FixThreshold)) {
        // apply fix to send timestamps
        CumulativeFixMagnitude = 0.0;
        cWackoSend = 0;
        for (i=0; i<N; i++) {
            if (fWackoSend[i]) {
                fWackySender = TRUE;
                CumulativeFixMagnitude += g_BadHalAdjustment;
                cWackoSend++;
            }
            sendstamps[i] -= (INT64)CumulativeFixMagnitude;
        }
    }
    if (cWackoRecv && (fractionaldevofrecvwackiness < FixThreshold)) {
        // apply fix to recv timestamps
        CumulativeFixMagnitude = 0.0;
        cWackoRecv = 0;
        for (i=0; i<N; i++) {
            if (fWackoRecv[i]) {
                fWackyReceiver = TRUE;
                CumulativeFixMagnitude += g_BadHalAdjustment;
                cWackoRecv++;
            }
            recvstamps[i] -= (INT64)CumulativeFixMagnitude;
        }
    }

    // set the globals to reflect our "fixed" values
    for (i=0; i<N; i++) {
        if (fWackySender) {
            GetLogEntry(&g_log, &currentRecord, i);
            currentRecord.TimeSent = sendstamps[i];
            SetLogEntry(&g_log, &currentRecord, i);
        }
        if (fWackyReceiver) {
            GetLogEntry(&g_log, &currentRecord, i);
            currentRecord.TimeReceived = recvstamps[i];
            SetLogEntry(&g_log, &currentRecord, i);
        }
    }
    if (fWackySender || fWackyReceiver) {
        printf("WARNING: I noticed some oddities among the timestamps on the");
        if (fWackySender) printf(" sender");
        if (fWackySender && fWackyReceiver) printf(" and");
        if (fWackyReceiver) printf(" receiver");
        printf(".\n");
        if (fWackySender) {
            printf("\t%d of them on the order of %fms each on the sender.\n",
                cWackoSend, meansendwackiness / 10000); }
        if (fWackyReceiver) {
            printf("\t%d of them on the order of %fms each on the receiver.\n",
                cWackoRecv, meanrecvwackiness / 10000); }
        printf("\tThey are caused by a malfunctioning clock on the afflicted machine.\n");
        printf("\tI have tried to compensate for them in the .log file.\n");
        NormalizeTimeStamps(); // we have to renormalize now
    }
    return FALSE;
}

DWORD WINAPI RSVPMonitor (LPVOID lpvThreadParm) {   
    DWORD dwResult = 0;
    ULONG status;
    BOOLEAN confirmed = FALSE;
    UINT64 ui64LastHi = 0,ui64Now = 0;
    FILETIME filetime;
    ULARGE_INTEGER ulargeint;
    BOOLEAN fResvGood = FALSE;

    // don't do anything until the control socket is established
    while (g_sockControl == INVALID_SOCKET) {
        Sleep(10);
    }

    while(TRUE){
        // send a HELLO message every once in a while
        GetSystemTimeAsFileTime(&filetime);
        memcpy(&ulargeint, &filetime, sizeof(FILETIME));
        ui64Now = ulargeint.QuadPart;
        if (ui64LastHi + 10000000*SECONDS_BETWEEN_HELLOS < ui64Now) {
            SendControlMessage(g_sockControl,MSGST_HELLO);
            ui64LastHi = ui64Now;
        }
        
        // get the RSVP statuscode, waiting for as long as it takes
        status = GetRsvpStatus(WSA_INFINITE,fd);

        if (g_state.Done) {
            ExitThread(1);
        }
        switch (status) {
            case WSA_QOS_TRAFFIC_CTRL_ERROR: // sad if we get this
                printf("RSVP-ERR: Reservation rejected by traffic control on server. Aborting.\n");
                SendControlMessage(g_sockControl,MSGST_RSVPERR);
                g_state.Done = TRUE;
                exit(1);
                break;
            case WSA_QOS_REQUEST_CONFIRMED:  // happy if we get this
                if (!confirmed) {
                    printf("RSVP: Reservation confirmed\n");
                    confirmed = TRUE;
                    fResvGood = TRUE;
                }
                break;
            case WSA_QOS_SENDERS:
                if (!fResvGood && !trans) {
                    printf("\nRSVP Monitor: WSA_QOS_SENDERS at t=%I64ds\n",
                        (GetUserTime() - timeStart) / 10000000);
                    fResvGood = TRUE;
                }
                break;
            case WSA_QOS_RECEIVERS:
                if (!fResvGood && trans) {
                    printf("\nRSVP Monitor: WSA_QOS_RECEIVERS at t=%I64ds\n",
                        (GetUserTime() - timeStart) / 10000000);
                    fResvGood = TRUE;
                }
                break;
            case WSA_QOS_NO_SENDERS: // the sender is now gone, so we stop
                if (fResvGood && !trans) {
                    printf("\nRSVP Monitor: WSA_QOS_NO_SENDERS at t=%I64ds\n",
                        (GetUserTime() - timeStart) / 10000000);
                    fResvGood = FALSE;
                }
                break;
            case WSA_QOS_NO_RECEIVERS: // means the sender is done, so he should exit
                if (fResvGood && trans) {
                    printf("\nRSVP Monitor: WSA_QOS_NO_RECEIVERS at t=%I64ds\n",
                        (GetUserTime() - timeStart) / 10000000);
                    fResvGood = FALSE;
                }
                break;
            default:
                break;
        }
        Sleep(1000); // check at most once per second
    }
    
    return dwResult;
} // RSVPMonitor()

DWORD WINAPI KeyboardMonitor(LPVOID lpvThreadParm) {
    DWORD dwResult = 0;
    char ch;
    while (TRUE) {
        ch = (CHAR) getchar();
        switch (ch) {
        case 'q':
            SendControlMessage(g_sockControl,MSGST_DONE);
            g_state.Done = TRUE;
            ExitThread(1);
            break;
        }
    }
    return 0;
}

DWORD WINAPI ControlSocketMonitor(LPVOID lpvThreadParm) {
    DWORD dwResult = 0;
    DWORD dwError, cbBuf = 0;
    DWORD dwAddrSize = MAX_STRING;
    char szAddr[MAX_STRING];
    char szBuf[MAX_STRING],szCommand[MAX_STRING], *pchStart, *pchEnd;
    int cch;
    char szT[MAX_STRING];
    char szT2[MAX_STRING];
    char * szHost;
    BOOL fSender;
    SOCKET sockControl, sockListen;
    SOCKADDR_IN sinmeControl, sinhimControl;
    PHOSTENT phostent;
    UINT64 ui64LastHello = 0;
    BOOL fDone = FALSE;
    BOOL fGotRate=FALSE, fGotSize=FALSE, fGotNum=FALSE;
    BOOL fSentReady =FALSE;

    // find out if we're the sender or receiver
    if (lpvThreadParm == NULL) fSender = FALSE;
    else fSender = TRUE;

    // if sender, copy the host address into our local host string
    if (fSender) {
        szHost = malloc(strlen((char *)lpvThreadParm) + 1);
        strcpy(szHost, (const char *)lpvThreadParm);
    }        

    // set up a control socket
    if (fSender) {
        sockControl = socket(AF_INET, SOCK_STREAM, 0);
    }
    else {
        sockListen = socket(AF_INET, SOCK_STREAM, 0);
    }
    
    // bind properly
    sinmeControl.sin_family = AF_INET;
    sinmeControl.sin_addr.s_addr = INADDR_ANY;
    sinhimControl.sin_family = AF_INET;
    if (fSender) {
        sinmeControl.sin_port = 0;
        // set up the sinhim structure
        if (atoi(szHost) > 0 )  {
            sinhimControl.sin_addr.s_addr = inet_addr(szHost);
        }
        else{
            if ((phostent=gethostbyname(szHost)) == NULL) {
                ErrorExit("bad host name",WSAGetLastError());
            }
            sinhimControl.sin_family = phostent->h_addrtype;
            memcpy(&(sinhimControl.sin_addr.s_addr), phostent->h_addr, phostent->h_length);
        }
        sinhimControl.sin_port = htons(CONTROL_PORT);
        dwError = bind(sockControl,(SOCKADDR*)&sinmeControl,sizeof(sinmeControl));
    }
    else { // receiver
        sinmeControl.sin_port = htons(CONTROL_PORT);
        dwError = bind(sockListen,(SOCKADDR*)&sinmeControl,sizeof(sinmeControl));
    }
    if (dwError == SOCKET_ERROR)
        ErrorExit("bind failed",WSAGetLastError());

    // now connect the socket
    sinhimControl.sin_family = AF_INET;
    if (fSender) {
        // if we're the sender, keep trying to connect until we get through
        dwAddrSize = MAX_STRING;
        dwError = WSAAddressToString((SOCKADDR *)&(sinhimControl),
                               sizeof(SOCKADDR_IN),
                               NULL,
                               szAddr,
                               &dwAddrSize);
        if (dwError == SOCKET_ERROR)
            ErrorExit("WSAAddressToString failed", WSAGetLastError());
        else
            strcpy(szHisAddr,szAddr);
        
        while (TRUE) {
            dwError = connect(sockControl,(SOCKADDR*)&sinhimControl,sizeof(sinhimControl));
            if (!dwError) {
                printf("control socket: connected to %s\n",szAddr);
                break;
            }
            dwError = WSAGetLastError();
            if (dwError != WSAECONNREFUSED) {
                ErrorExit("connect() failed",dwError);
            }
            Sleep(500); // wait a half second between attempts
        }
    }
    else {
        // if we're the receiver, listen / accept
        if (listen(sockListen, SOMAXCONN) == SOCKET_ERROR) {
            ErrorExit("listen() failed", WSAGetLastError());
        }

        sockControl = accept(sockListen, (SOCKADDR*)&sinhimControl, &dwAddrSize);
        // once we've accepted, close the listen socket
        closesocket(sockListen);
        if ((INT_PTR)sockControl < 0) {
            ErrorExit("accept() failed",WSAGetLastError());
        }
        
        dwAddrSize = MAX_STRING;    
        dwError = WSAAddressToString((SOCKADDR *)&(sinhimControl),
                               sizeof(SOCKADDR_IN),
                               NULL,
                               szAddr,
                               &dwAddrSize);
        if (dwError == SOCKET_ERROR)
            ErrorExit("WSAAddressToString failed", WSAGetLastError());
        else
            strcpy(szHisAddr, szAddr);
        
        printf("control socket: accepted connection from %s\n",szAddr);
    }

    // set our global control socket variable
    g_sockControl = sockControl;

    // record my name
    dwAddrSize = sizeof(SOCKADDR_IN);
    getsockname(sockControl,(SOCKADDR *)&(sinmeControl),&dwAddrSize);
    dwAddrSize = MAX_STRING;    
    dwError = WSAAddressToString((SOCKADDR *)&(sinmeControl),
                    sizeof(SOCKADDR_IN), NULL, szAddr, &dwAddrSize);
    if (dwError == SOCKET_ERROR)
        ErrorExit("WSAAddressToString failed", WSAGetLastError());
    else
        strcpy(szMyAddr, szAddr);  
        
    // exchange version information
    sprintf(szBuf, "%s %s", MSGST_VER, VERSION_STRING);
    SendControlMessage(sockControl, szBuf);
    
    // now that we're all set, do the actual work of the control socket
    while (!fDone) {
        ZeroMemory(szBuf,MAX_STRING);
        dwError = cbBuf = recv(sockControl, szBuf, MAX_STRING, 0);
        pchStart = szBuf;
        pchEnd = szBuf + cbBuf;
        if (dwError == 0) { // the connection's been gracefully closed
            fDone = TRUE;
            closesocket(sockControl);
            g_fOtherSideFinished=TRUE;
            ExitThread(0);
        }
        if (dwError == SOCKET_ERROR) {
            dwError = WSAGetLastError();
            if (dwError == WSAECONNRESET) {
                printf("\ncontrol socket: connection reset by peer");
                printf("\n\t%I64us since last HELLO packet received",
                    (GetUserTime() - ui64LastHello)/10000000);
                printf("\n\t%I64us since start",
                    (GetUserTime() - timeStart)/10000000);
                g_state.Done = TRUE;
                fDone = TRUE;
                g_fOtherSideFinished = TRUE;
                closesocket(sockControl);
                ExitThread(1);
            }
            else {
                printf("\ncontrol socket: error in recv: %d\n",dwError);
                g_state.Done = TRUE;
                fDone = TRUE;
                g_fOtherSideFinished = TRUE;
                closesocket(sockControl);
                ExitThread(1);
            }
            continue;
        }
        while (pchStart < pchEnd) {
            ZeroMemory(szCommand,MAX_STRING);
            // consume the first command and act on it
            if (pchEnd > szBuf + cbBuf) break;
            pchEnd = strchr(pchStart, MSGCH_DELIMITER);
            if (pchEnd == NULL) break;
            strncpy(szCommand,pchStart,pchEnd - pchStart);
            if (strcmp(szCommand,MSGST_HELLO) == 0) {
                // update last hello time
                ui64LastHello = GetUserTime();
                // i should do something like set a timer here that sleeps until a certain timeout
                // passes, at which point it aborts our transfer
            }
            if (strcmp(szCommand,MSGST_ERROR) == 0) {
                // the other guy's had an error, so we stop and tell him to abort
                g_fOtherSideFinished = TRUE;
                g_state.Done = TRUE;
                fDone = TRUE;
                SendControlMessage(sockControl,MSGST_ABORT);
                closesocket(sockControl);
                ExitThread(1);
            }
            if (strcmp(szCommand,MSGST_ABORT) == 0) {
                // we're told to abort, so do so
                g_fOtherSideFinished = TRUE;
                g_state.Done = TRUE;
                fDone = TRUE;
                closesocket(sockControl);
                ExitThread(1);
            }
            if (strcmp(szCommand,MSGST_DONE) == 0) {
                // we're told the other guy's done, so therefore are we
                closesocket(sockControl);
                g_fOtherSideFinished = TRUE;
                g_state.Done = TRUE;
                fDone = TRUE;
                ExitThread(1);
            }
            if (strcmp(szCommand,MSGST_RSVPERR) == 0) {
                // we're told the other guy got an rsvp error, so we abort the whole program
                closesocket(sockControl);
                g_fOtherSideFinished = TRUE;
                g_state.Done = TRUE;
                fDone = TRUE;
                exit(1);
            }
            if (strncmp(szCommand,MSGST_SIZE,4) == 0) {
                // the sender is telling us how big the buffers are
                sscanf(szCommand,"%s %d",szT, &g_params.buflen);
                fGotSize = TRUE;
            }
            if (strncmp(szCommand,MSGST_RATE,4) == 0) {
                // the sender is telling us how fast the buffers are coming
                sscanf(szCommand, "%s %d",szT, &g_params.TokenRate);
                fGotRate = TRUE;
            }
            if (strncmp(szCommand,MSGST_NUM,3) == 0) {
                // the sender is telling us how many buffers it's sending
                sscanf(szCommand, "%s %d",szT, &g_params.nbuf);
                totalBuffers = g_params.nbuf;
                fGotNum = TRUE;
            }
            if (strncmp(szCommand,MSGST_VER,3) == 0) {
                sscanf(szCommand, "%s %s",szT, szT2);
                if (strcmp(szT2,VERSION_STRING) != 0) {
                    printf("WARNING: remote machine using different version of qtcp: %s vs. %s\n", 
                        szT2,VERSION_STRING);
                }
            }
            if (trans) {
                if (strcmp(szCommand,MSGST_READY) == 0) {
                    g_fReadyForXmit = TRUE;
                }
            }
            else {
                if (!fSentReady && fGotRate && fGotSize && fGotNum) {
                    SendControlMessage(sockControl, MSGST_READY);
                    fSentReady = TRUE;
                    g_fReadyForXmit = TRUE;
                }
            }
            pchStart = pchEnd + 1;
            pchEnd = szBuf + cbBuf;
        }    
    }
    return 0;
}

int SendControlMessage(SOCKET sock, char * szMsg) {
    int iResult;
    char szBuf[MAX_STRING];

    sprintf(szBuf,"%s%c",szMsg,MSGCH_DELIMITER);
    iResult = send (sock, szBuf, strlen(szBuf), 0);

    if (iResult == SOCKET_ERROR) {
        return WSAGetLastError();
    }
    return iResult;
}

void ErrorExit(char *msg, DWORD dwErrorNumber) {
    fprintf(stderr,"ERROR: %d\n",dwErrorNumber);
    if (msg != NULL)
        fprintf(stderr,"\t%s\n",msg);
    else {
        switch(dwErrorNumber) {
        case WSAEFAULT:
            fprintf(stderr,"\tWSAEFAULT: Buffer too small to contain name\n");
            break;
        case WSAEINVAL:
            fprintf(stderr,"\tWSAEINVAL: Invalid socket address\n");
            break;
        case WSANOTINITIALISED:
            fprintf(stderr,"\tWSANOTINITIALIZED: WSA Not initialized\n");
            break;
        default:
            fprintf(stderr,"\tUnknown error\n");
            break;
        }
    }
    SendControlMessage(g_sockControl, MSGST_ABORT);
    
    DestroyLog(&g_log);
    WSACleanup();
    exit(1);
    _exit(1);
}

// some math utility functions

// comparison for doubles (to use in qsort)
int __cdecl compare( const void *arg1, const void *arg2 )
{
    DOUBLE dTemp;
    DOUBLE d1 = * (DOUBLE *) arg1;
    DOUBLE d2 = * (DOUBLE *) arg2;
    dTemp = d1 - d2;
    if (dTemp < 0) return -1;
    if (dTemp == 0) return 0;
    else 
        return 1;

}
// comparison for ints (to use in qsort)
int __cdecl compareint( const void *arg1, const void *arg2 )
{
    int nTemp;
    int n1 = * (int *) arg1;
    int n2 = * (int *) arg2;
    nTemp = n1 - n2;
    if (nTemp < 0) return -1;
    if (nTemp == 0) return 0;
    else 
      return 1;
}
// comparison for int64s (to use in qsort)
int __cdecl compareI64( const void *arg1, const void *arg2 )
{
    INT64 nTemp;
    INT64 n1 = * (INT64 *) arg1;
    INT64 n2 = * (INT64 *) arg2;
    nTemp = n1 - n2;
    if (nTemp < 0) return -1;
    if (nTemp == 0) return 0;
    else return 1;
}


#define EPS 1.0e-7
// sum up error function for given value of b
double rofunc(double b, int N, double yt[], double xt[], double * paa, double * pabdevt) {
    int i;
    double *pfT;
    double d, sum=0.0;
    double aa = *paa;
    double abdevt = *pabdevt;

    pfT = malloc(sizeof(double) * N);
    for (i = 0; i < N; i++) pfT[i] = yt[i]-b*xt[i];
    qsort(pfT, N, sizeof(DOUBLE), compare);
    if (N & 1) { // odd N
        aa = pfT[(N+1) / 2];
    }
    else {
        i = N / 2;
        aa = 0.5 * (pfT[i] + pfT[i+1]);
    }
    abdevt = 0.0;
    for (i = 0; i<N; i++) {
        d = yt[i] - (b*xt[i]+aa);
        abdevt += fabs(d);
        if (yt[i] != 0.0) d /= fabs(yt[i]);
        if (fabs(d) > EPS) sum += (d >= 0.0 ? xt[i]: -xt[i]);
    }
    *paa = aa;
    *pabdevt = abdevt;
    free(pfT);
    return sum;
}

#define SIGN(a,b) ((b) >= 0 ? fabs(a) : fabs(-a))

void medfit(double x[], double y[], int N, double *a, double *b, double *abdev) {
    // fit y = a + bx to least absolute deviation. abdev is mean absolute deviation.
    // incoming, a and b are treated as starting guesses
    int i;
    double *xt = x;
    double *yt = y;
    double sx, sy, sxy, sxx, chisq;
    double del, sigb;
    double bb, b1, b2, aa, abdevt, f, f1, f2, temp;

    sx = sy = sxy = sxx = chisq = 0.0;
    // we find chisq fit to use as starting guess
    for (i=0; i<N; i++) {
        sx += x[i];
        sy += y[i];
        sxy += x[i]*y[i];
        sxx += x[i]*x[i];
    }
    del = N*sxx - sx*sx;
    aa = (sxx*sy-sx*sxy) / del;
    bb = (N*sxy - sx*sy) / del;
    // do the absolute deviation fit, if we're supposed to.
    if (g_params.SkewFitMode == 2) { 
        for (i=0; i<N; i++)
            chisq += (temp=y[i]-(aa+bb*x[i]), temp*temp);
        sigb = sqrt(chisq/del);
        b1 = bb;
        f1 = rofunc(b1, N, yt, xt, &aa, &abdevt);
        // guess the bracket as 3 sigma away in downhill direction from f1
        b2 = bb + SIGN(3.0 * sigb, f1);
        f2 = rofunc(b2, N, yt, xt, &aa, &abdevt);
        if (b2 == b1) {
            *a = aa;
            *b = bb;
            *abdev = abdevt / N;
            return;
        }
        // Bracketing
        while ((f1*f2) > 0.0) {
            if (fabs(f1) < fabs(f2))
                f1 = rofunc(b1 += 1.6*(b1-b2),N,yt,xt,&aa,&abdevt);
            else
                f2 = rofunc(b2 += 1.6*(b2-b1),N,yt,xt,&aa,&abdevt);
        }
        
        sigb = 0.000001 * sigb; // refine
        while (fabs(b2 - b1) > sigb) {
            bb = b1 + 0.5 * (b2 - b1);
            if (bb == b1 || bb == b2) break;
            f = rofunc(bb, N, yt, xt, &aa, &abdevt);
            if (f*f1 >= 0.0) {
                f1 = f;
                b1 = bb;
            } else {
                f2 = f;
                b2 = bb;
            }
        }
    }
    
    *a = aa;
    *b = bb;
    *abdev = abdevt / N;
}

double mode(const double data[], const int N) {
    // finds and returns the mode of the N points in data
    double * sorted;
    double mode, cur=0;
    int cMode, cCur;
    int i;

    sorted = malloc(N * sizeof(double));

    for (i=0; i<N; i++) sorted[i] = data[i];
    qsort(sorted, N, sizeof(double), compare);
    mode = sorted[0];
    cMode = cCur = 0;
    for (i=0; i<N; i++) {
        if (cCur > cMode) {
            mode = cur;
            cMode = cCur;
        }
        if (sorted[i] == mode) {
            cMode++;
        } else {
            if (sorted[i] == cur) cCur++;
            else {
                cur = sorted[i];
                cCur = 1;
            }
        }
    }
    
    free(sorted);
    return mode;
}

void RemoveDuplicates(int rg[], int * pN) {
    // this removes duplicates from the array passed in and returns it with *pN = #remaining
    // it makes no guarantees about elements after rg[#remaining]
    int *pNewArray;
    int cNew;
    int i;
    
    qsort(rg,*pN,sizeof(int),compareint);
    pNewArray = malloc(sizeof(int) * *pN);
    pNewArray[0] = rg[0];
    cNew = 1;
    for (i=1; i<*pN; i++) {
        if (rg[i] != pNewArray[cNew - 1]) {
            pNewArray[cNew++] = rg[i];
        }
    }
    *pN = cNew;
    for (i=0; i<cNew; i++)
        rg[i] = pNewArray[i];
}

void RemoveDuplicatesI64(INT64 rg[], int * pN) {
    // this removes duplicates from the array passed in and returns it with *pN = #remaining
    // it makes no guarantees about elements after rg[#remaining]
    INT64 *pNewArray;
    int cNew;
    int i;
    
    qsort(rg,*pN,sizeof(INT64),compareI64);
    pNewArray = malloc(sizeof(INT64) * *pN);
    pNewArray[0] = rg[0];
    cNew = 1;
    for (i=1; i<*pN; i++) {
        if (rg[i] != pNewArray[cNew - 1]) {
            pNewArray[cNew++] = rg[i];
        }
    }
    *pN = cNew;
    for (i=0; i<cNew; i++)
        rg[i] = pNewArray[i];
}

void PrintFlowspec(LPFLOWSPEC lpfs) {
    printf("TokenRate:          %lu bytes/sec\n",lpfs->TokenRate);
    printf("TokenBucketSize:    %lu bytes\n",lpfs->TokenBucketSize);
    printf("PeakBandwidth:      %lu bytes/sec\n",lpfs->PeakBandwidth);
    printf("Latency:            %lu microseconds\n",lpfs->Latency);
    printf("DelayVariation:     %lu microseconds\n",lpfs->DelayVariation);
    printf("ServiceType:        %X\n",lpfs->ServiceType);
    printf("MaxSduSize:         %lu bytes\n",lpfs->MaxSduSize);
    printf("MinimumPolicedSize: %lu bytes\n",lpfs->MinimumPolicedSize);
}