/* USB Benchmark for libusb-win32

 Copyright (C) 2010 Travis Robinson. <libusbdotnet@gmail.com>
 website: http://sourceforge.net/projects/libusb-win32
 
 This program is free software; you can redistribute it and/or modify it
 under the terms of the GNU Lesser General Public License as published by 
 the Free Software Foundation; either version 2 of the License, or 
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful, but 
 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 License for more details.
 
 You should have received a copy of the GNU Lesser General Public License
 along with this program; if not, please visit www.gnu.org.
*/

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>

#include "lusb0_usb.h"

#define _BENCHMARK_VER_ONLY
#include "benchmark_rc.rc"

#define MAX_OUTSTANDING_TRANSFERS 10

// This is used only in VerifyData() for display information
// about data validation mismatches.
#define CONVDAT(format,...) printf("[data-mismatch] " format,__VA_ARGS__)

// All output is directed through these macros.
//
#define LOG(LogTypeString,format,...)  printf("%s[" __FUNCTION__ "] "format, LogTypeString, __VA_ARGS__)
#define CONERR(format,...) LOG("Error:",format,__VA_ARGS__)
#define CONMSG(format,...) LOG("",format,__VA_ARGS__)
#define CONWRN(format,...) LOG("Warn:",format,__VA_ARGS__)
#define CONDBG(format,...) LOG("",format,__VA_ARGS__)

#define CONERR0(message) CONERR("%s", message)
#define CONMSG0(message) CONMSG("%s", message)
#define CONWRN0(message) CONWRN("%s", message)
#define CONDBG0(message) CONDBG("%s", message)

// This is the libusb-win32 return code for a transfer that timed out.
#define TRANSFER_TIMEDOUT -116

// Custom vendor requests that must be implemented in the benchmark firmware.
// Test selection can be bypassed with the "notestselect" argument.
//
enum BENCHMARK_DEVICE_COMMANDS
{
    SET_TEST = 0x0E,
    GET_TEST = 0x0F,
};

// Tests supported by the official benchmark firmware.
//
enum BENCHMARK_DEVICE_TEST_TYPE
{
    TestTypeNone	= 0x00,
    TestTypeRead	= 0x01,
    TestTypeWrite	= 0x02,
    TestTypeLoop	= TestTypeRead|TestTypeWrite,
};

// This software was mainly created for testing the libusb-win32 kernel & user driver.
enum BENCHMARK_TRANSFER_MODE
{
	// Tests for the libusb-win32 sync transfer function.
    TRANSFER_MODE_SYNC,

	// Test for async function, iso transfers, and queued transfers
    TRANSFER_MODE_ASYNC,
};

// Holds all of the information about a test.
struct BENCHMARK_TEST_PARAM
{
    // User configurable value set from the command line.
    //
    INT Vid;			// Vendor ID
    INT Pid;			// Porduct ID
    INT Intf;			// Interface number
	INT	Altf;			// Alt Interface number
    INT Ep;				// Endpoint number (1-15)
    INT Refresh;		// Refresh interval (ms)
    INT Timeout;		// Transfer timeout (ms)
    INT Retry;			// Number for times to retry a timed out transfer before aborting
    INT BufferSize;		// Number of bytes to transfer
    INT BufferCount;	// Number of outstanding asynchronous transfers
    BOOL NoTestSelect;	// If true, don't send control message to select the test type.
    BOOL UseList;		// Show the user a device list and let them choose a benchmark device. 
	INT IsoPacketSize; // Isochronous packet size (defaults to the endpoints max packet size)
    INT Priority;		// Priority to run this thread at.
	BOOL Verify;		// Only for loop and read test. If true, verifies data integrity. 
	BOOL VerifyDetails;	// If true, prints detailed information for each invalid byte.
    enum BENCHMARK_DEVICE_TEST_TYPE TestType;	// The benchmark test type.
	enum BENCHMARK_TRANSFER_MODE TransferMode;	// Sync or Async

    // Internal value use during the test.
    //
    usb_dev_handle* DeviceHandle;
	struct usb_device* Device;
    BOOL IsCancelled;
    BOOL IsUserAborted;

	BYTE* VerifyBuffer;		// Stores the verify test pattern for 1 packet.
	WORD VerifyBufferSize;	// Size of VerifyBuffer
};

// The benchmark transfer context used for asynchronous transfers.  see TransferAsync().
struct BENCHMARK_TRANSFER_HANDLE
{
	VOID* Context;
	BOOL InUse;
	CHAR* Data;
	INT DataMaxLength;
	INT ReturnCode;
};

// Holds all of the information about a transfer.
struct BENCHMARK_TRANSFER_PARAM
{
    struct BENCHMARK_TEST_PARAM* Test;

    HANDLE ThreadHandle;
    DWORD ThreadID;
	struct usb_endpoint_descriptor Ep;
	INT IsoPacketSize;
    BOOL IsRunning;

    LONGLONG TotalTransferred;
	LONG LastTransferred;

    LONG Packets;
    DWORD StartTick;
    DWORD LastTick;
    DWORD LastStartTick;

    INT TotalTimeoutCount;
    INT RunningTimeoutCount;
	
	INT TotalErrorCount;
	INT RunningErrorCount;

	INT ShortTransferCount;

	INT TransferHandleNextIndex;
	INT TransferHandleWaitIndex;
	INT OutstandingTransferCount;

	struct BENCHMARK_TRANSFER_HANDLE TransferHandles[MAX_OUTSTANDING_TRANSFERS];

	// Placeholder for end of structure; this is where the raw data for the
	// transfer buffer is allocated.
	//
    BYTE Buffer[0];
};

// Benchmark device api.
struct usb_dev_handle* Bench_Open(WORD vid,	WORD pid, INT interfaceNumber, INT altInterfaceNumber, struct usb_device** deviceForHandle);
int Bench_SetTestType(struct usb_dev_handle* dev, enum BENCHMARK_DEVICE_TEST_TYPE testType, int intf);
int Bench_GetTestType(struct usb_dev_handle* dev, enum BENCHMARK_DEVICE_TEST_TYPE* testType, int intf);

// Critical section for running status. 
CRITICAL_SECTION DisplayCriticalSection;

// Finds the interface for [interface_number] in a libusb-win32 config descriptor.
// If first_interface is not NULL, it is set to the first interface in the config.
//
struct usb_interface_descriptor* usb_find_interface(struct usb_config_descriptor* config_descriptor,
	INT interface_number,
	INT alt_interface_number,
	struct usb_interface_descriptor** first_interface);

// Internal function used by the benchmark application.
void ShowHelp(void);
void ShowCopyright(void);
void SetTestDefaults(struct BENCHMARK_TEST_PARAM* test);
char* GetParamStrValue(const char* src, const char* paramName);
BOOL GetParamIntValue(const char* src, const char* paramName, INT* returnValue);
int ValidateBenchmarkArgs(struct BENCHMARK_TEST_PARAM* testParam);
int ParseBenchmarkArgs(struct BENCHMARK_TEST_PARAM* testParams, int argc, char **argv);
void FreeTransferParam(struct BENCHMARK_TRANSFER_PARAM** testTransferRef);
struct BENCHMARK_TRANSFER_PARAM* CreateTransferParam(struct BENCHMARK_TEST_PARAM* test, int endpointID);
void GetAverageBytesSec(struct BENCHMARK_TRANSFER_PARAM* transferParam, DOUBLE* bps);
void GetCurrentBytesSec(struct BENCHMARK_TRANSFER_PARAM* transferParam, DOUBLE* bps);
void ShowRunningStatus(struct BENCHMARK_TRANSFER_PARAM* transferParam);
void ShowTestInfo(struct BENCHMARK_TEST_PARAM* testParam);
void ShowTransferInfo(struct BENCHMARK_TRANSFER_PARAM* transferParam);

void WaitForTestTransfer(struct BENCHMARK_TRANSFER_PARAM* transferParam);
void ResetRunningStatus(struct BENCHMARK_TRANSFER_PARAM* transferParam);

// The thread transfer routine.
DWORD TransferThreadProc(struct BENCHMARK_TRANSFER_PARAM* transferParams);

#define TRANSFER_DISPLAY(TransferParam, ReadingString, WritingString) \
	((TransferParam->Ep.bEndpointAddress & USB_ENDPOINT_DIR_MASK) ? ReadingString : WritingString)

#define INC_ROLL(IncField, RollOverValue) if ((++IncField) >= RollOverValue) IncField = 0

#define ENDPOINT_TYPE(TransferParam) (TransferParam->Ep.bmAttributes & 3)
const char* TestDisplayString[] = {"None", "Read", "Write", "Loop", NULL};
const char* EndpointTypeDisplayString[] = {"Control", "Isochronous", "Bulk", "Interrupt", NULL};

void SetTestDefaults(struct BENCHMARK_TEST_PARAM* test)
{
    memset(test,0,sizeof(struct BENCHMARK_TEST_PARAM));

    test->Ep			= 0x00;
    test->Vid			= 0x0666;
    test->Pid			= 0x0001;
    test->Refresh		= 1000;
    test->Timeout		= 5000;
    test->TestType		= TestTypeLoop;
    test->BufferSize	= 4096;
    test->BufferCount   = 1;
    test->Priority		= THREAD_PRIORITY_NORMAL;
}

struct usb_interface_descriptor* usb_find_interface(struct usb_config_descriptor* config_descriptor,
	INT interface_number,
	INT alt_interface_number,
	struct usb_interface_descriptor** first_interface)
{
	struct usb_interface_descriptor* intf;
	int intfIndex;

	if (first_interface) 
		*first_interface = NULL;

	if (!config_descriptor) return NULL;

	for (intfIndex = 0; intfIndex < config_descriptor->bNumInterfaces; intfIndex++)
	{
		if (config_descriptor->interface[intfIndex].num_altsetting)
		{
			intf = &config_descriptor->interface[intfIndex].altsetting[0];
			if ((first_interface) && *first_interface == NULL)
				*first_interface = intf;

			if (intf->bInterfaceNumber == interface_number && 
				(alt_interface_number==-1 || intf->bAlternateSetting==alt_interface_number))
			{
				return intf;
			}
		}
	}

	return NULL;
}
struct usb_dev_handle* Bench_Open(WORD vid, WORD pid, INT interfaceNumber, INT altInterfaceNumber, struct usb_device** deviceForHandle)
{
    struct usb_bus* bus;
    struct usb_device* dev;
    struct usb_dev_handle* udev;

    for (bus = usb_get_busses(); bus; bus = bus->next)
    {
        for (dev = bus->devices; dev; dev = dev->next)
        {
            if (dev->descriptor.idVendor == vid && dev->descriptor.idProduct == pid)
            {
				if ((udev = usb_open(dev)))
				{
					if (dev->descriptor.bNumConfigurations)
					{
						if (usb_find_interface(&dev->config[0], interfaceNumber, altInterfaceNumber, NULL) != NULL)
						{
							if (deviceForHandle) *deviceForHandle = dev;
							return udev;
						}
					}

					usb_close(udev);
				}
            }
        }
    }
    return NULL;
}

int Bench_SetTestType(struct usb_dev_handle* dev, enum BENCHMARK_DEVICE_TEST_TYPE testType, int intf)
{
    char buffer[1];
    int ret = 0;

    ret = usb_control_msg(dev,
                          USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN,
                          SET_TEST, testType, intf,
                          buffer, 1,
                          1000);
    return ret;
}

int Bench_GetTestType(struct usb_dev_handle* dev, enum BENCHMARK_DEVICE_TEST_TYPE* testType, int intf)
{
    char buffer[1];
    int ret = 0;

    ret = usb_control_msg(dev,
                          USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN,
                          GET_TEST, 0, intf,
                          buffer, 1,
                          1000);

    if (ret == 1)
        *testType = buffer[0];

    return ret;
}
enum TRANSFER_VERIFY_STATE
{
	TVS_START,
	TVS_KEY,
	TVS_DATA,
	TVS_FIND_START,

};

INT VerifyData(struct BENCHMARK_TRANSFER_PARAM* transferParam, BYTE* data, INT dataLength)
{

	WORD verifyDataSize = transferParam->Test->VerifyBufferSize;
	BYTE* verifyData = transferParam->Test->VerifyBuffer;
	BYTE keyC = 0;
	BOOL seedKey = TRUE;
	INT dataLeft = dataLength;
	INT dataIndex = 0;
	INT packetIndex = 0;
	INT verifyIndex = 0;

	while(dataLeft > 1)
	{
		verifyDataSize = dataLeft > transferParam->Test->VerifyBufferSize ? transferParam->Test->VerifyBufferSize : dataLeft;

		if (seedKey)
			keyC = data[dataIndex+1];
		else
		{
			if (data[dataIndex+1]==0)
			{
				keyC=0;
			}
			else
			{
				keyC++;
			}
		}
		seedKey = FALSE;
		// Index 0 is always 0.
		// The key is always at index 1
		verifyData[1] = keyC;
		if (memcmp(&data[dataIndex],verifyData,verifyDataSize) != 0)
		{
			// Packet verification failed.

			// Reset the key byte on the next packet.
			seedKey = TRUE;

			CONVDAT("data mismatch packet-index=%d data-index=%d\n", packetIndex, dataIndex);

			if (transferParam->Test->VerifyDetails)
			{
				for (verifyIndex=0; verifyIndex<verifyDataSize; verifyIndex++)
				{
					if (verifyData[verifyIndex] == data[dataIndex + verifyIndex])
						continue;

					CONVDAT("packet-offset=%d expected %02Xh got %02Xh\n",
						verifyIndex,
						verifyData[verifyIndex],
						data[dataIndex+verifyIndex]);

				}
			}

		}

		// Move to the next packet.
		packetIndex++;
		dataLeft -= verifyDataSize;
		dataIndex+= verifyDataSize;

	}

	return 0;
}

int TransferSync(struct BENCHMARK_TRANSFER_PARAM* transferParam)
{
	int ret;
	if (transferParam->Ep.bEndpointAddress & USB_ENDPOINT_DIR_MASK)
	{
		ret = usb_bulk_read(
				  transferParam->Test->DeviceHandle, transferParam->Ep.bEndpointAddress,
				  transferParam->Buffer, transferParam->Test->BufferSize,
				  transferParam->Test->Timeout);
	}
	else
	{
		ret = usb_bulk_write(
				  transferParam->Test->DeviceHandle, transferParam->Ep.bEndpointAddress,
				  transferParam->Buffer, transferParam->Test->BufferSize,
				  transferParam->Test->Timeout);
	}

	return ret;
}

int TransferAsync(struct BENCHMARK_TRANSFER_PARAM* transferParam, struct BENCHMARK_TRANSFER_HANDLE** handleRef)
{
	int ret;
	struct BENCHMARK_TRANSFER_HANDLE* handle;

	*handleRef = NULL;

	// Submit transfers until the maximum number of outstanding transfer(s) is reached.
	while (transferParam->OutstandingTransferCount < transferParam->Test->BufferCount)
	{
		// Get the next available benchmark transfer handle.
		*handleRef = handle = &transferParam->TransferHandles[transferParam->TransferHandleNextIndex];

		// If a libusb-win32 transfer context hasn't been setup for this benchmark transfer
		// handle, do it now.
		//
		if (!handle->Context)
		{
			// Data buffer(s) are located at the end of the transfer param.
			handle->Data = transferParam->Buffer + (transferParam->TransferHandleNextIndex * transferParam->Test->BufferSize);
			handle->DataMaxLength = transferParam->Test->BufferSize;

			
			switch (ENDPOINT_TYPE(transferParam))
			{
			case USB_ENDPOINT_TYPE_ISOCHRONOUS:
				ret = usb_isochronous_setup_async(transferParam->Test->DeviceHandle, 
					&handle->Context,
					transferParam->Ep.bEndpointAddress,
					transferParam->IsoPacketSize ? transferParam->IsoPacketSize : transferParam->Ep.wMaxPacketSize);
				break;
			case USB_ENDPOINT_TYPE_BULK:
				ret = usb_bulk_setup_async(transferParam->Test->DeviceHandle,
					&handle->Context,
					transferParam->Ep.bEndpointAddress);
				break;
			case USB_ENDPOINT_TYPE_INTERRUPT:
				ret = usb_interrupt_setup_async(transferParam->Test->DeviceHandle,
					&handle->Context,
					transferParam->Ep.bEndpointAddress);
				break;
			default:
				ret = -1;
				break;
			}

			if (ret < 0) 
			{
				CONERR("failed creating transfer context ret=%d\n",ret);
				goto Done;
			}
		}


		// Submit this transfer now.
		handle->ReturnCode = ret = usb_submit_async(handle->Context, handle->Data, handle->DataMaxLength);
		if (ret < 0) goto Done;

		// Mark this handle has InUse.
		handle->InUse = TRUE;

		// When transfers ir successfully submitted, OutstandingTransferCount goes up; when
		// they are completed it goes down.
		//
		transferParam->OutstandingTransferCount++;

		// Move TransferHandleNextIndex to the next available transfer.
		INC_ROLL(transferParam->TransferHandleNextIndex, transferParam->Test->BufferCount);

	}

	// If the number of outstanding transfers has reached the limit, wait for the 
	// oldest outstanding transfer to complete.
	//
	if (transferParam->OutstandingTransferCount == transferParam->Test->BufferCount)
	{
		// TransferHandleWaitIndex is the index of the oldest outstanding transfer.
		*handleRef = handle = &transferParam->TransferHandles[transferParam->TransferHandleWaitIndex];

		// Only wait, cancelling & freeing is handled by the caller.
		handle->ReturnCode = ret = usb_reap_async_nocancel(handle->Context, transferParam->Test->Timeout);

		if (ret < 0) goto Done;

		// Mark this handle has no longer InUse.
		handle->InUse = FALSE;

		// When transfers ir successfully submitted, OutstandingTransferCount goes up; when
		// they are completed it goes down.
		//
		transferParam->OutstandingTransferCount--;

		// Move TransferHandleWaitIndex to the oldest outstanding transfer.
		INC_ROLL(transferParam->TransferHandleWaitIndex, transferParam->Test->BufferCount);
	}

Done:
	return ret;
}

DWORD TransferThreadProc(struct BENCHMARK_TRANSFER_PARAM* transferParam)
{
    int ret, i;
	struct BENCHMARK_TRANSFER_HANDLE* handle;
	char* data;
    transferParam->IsRunning = TRUE;

    while (!transferParam->Test->IsCancelled)
    {
		data = NULL;
		handle = NULL;

		if (transferParam->Test->TransferMode == TRANSFER_MODE_SYNC)
		{
			ret = TransferSync(transferParam);
			if (ret >= 0) data = transferParam->Buffer;
		}
		else if (transferParam->Test->TransferMode == TRANSFER_MODE_ASYNC)
		{
			ret = TransferAsync(transferParam, &handle);
			if ((handle) && ret >= 0) data = handle->Data;
		}
		else
		{
            CONERR("invalid transfer mode %d\n",transferParam->Test->TransferMode);
			goto Done;
		}
        if (ret < 0)
        {
			// The user pressed 'Q'.
            if (transferParam->Test->IsUserAborted) break;

			// Transfer timed out
            if (ret == TRANSFER_TIMEDOUT)
            {
                transferParam->TotalTimeoutCount++;
                transferParam->RunningTimeoutCount++;
                CONWRN("Timeout #%d %s on Ep%02Xh..\n",
                       transferParam->RunningTimeoutCount,
                       TRANSFER_DISPLAY(transferParam,"reading","writing"),
                       transferParam->Ep.bEndpointAddress);

                if (transferParam->RunningTimeoutCount > transferParam->Test->Retry)
                    break;
            }
            else
            {
				// An error (other than a timeout) occured.

				// usb_strerror()is not thread safe and should not be used
				// in a multi-threaded app.  It's used here because
				// this is a test program.
				//

				transferParam->TotalErrorCount++;
                transferParam->RunningErrorCount++;
				CONERR("failed %s! %d of %d ret=%d: %s\n",
					TRANSFER_DISPLAY(transferParam,"reading","writing"),
					transferParam->RunningErrorCount,
					transferParam->Test->Retry+1,
					ret,
					usb_strerror());

				usb_resetep(transferParam->Test->DeviceHandle, transferParam->Ep.bEndpointAddress);

                if (transferParam->RunningErrorCount > transferParam->Test->Retry)
                    break;

            }
			ret = 0;
        }
        else
        {
			if (ret < transferParam->Test->BufferSize && !transferParam->Test->IsCancelled)
			{
				if (ret > 0)
				{
					transferParam->ShortTransferCount++;
					CONWRN("Short transfer on Ep%02Xh expected %d got %d.\n",
						transferParam->Ep.bEndpointAddress,
						transferParam->Test->BufferSize,
						ret);
				}
				else
				{
					CONWRN("Zero-length transfer on Ep%02Xh expected %d.\n",
						transferParam->Ep.bEndpointAddress,
						transferParam->Test->BufferSize);

					transferParam->TotalErrorCount++;
					transferParam->RunningErrorCount++;
					if (transferParam->RunningErrorCount > transferParam->Test->Retry)
						break;
					usb_resetep(transferParam->Test->DeviceHandle, transferParam->Ep.bEndpointAddress);
				}
			}
			else
			{
				transferParam->RunningErrorCount = 0;
				transferParam->RunningTimeoutCount = 0;
			}

			if ((transferParam->Test->Verify) && 
				(transferParam->Ep.bEndpointAddress & USB_ENDPOINT_DIR_MASK))
			{
				VerifyData(transferParam, data, ret);
			}
        }

        EnterCriticalSection(&DisplayCriticalSection);

        if (!transferParam->StartTick && transferParam->Packets >= 0)
        {
            transferParam->StartTick = GetTickCount();
			transferParam->LastStartTick	= transferParam->StartTick;
            transferParam->LastTick			= transferParam->StartTick;

			transferParam->LastTransferred = 0;
            transferParam->TotalTransferred = 0;
            transferParam->Packets = 0;
        }
        else
        {
			if (!transferParam->LastStartTick)
			{
				transferParam->LastStartTick	= transferParam->LastTick;
				transferParam->LastTransferred = 0;
			}
            transferParam->LastTick			= GetTickCount();
 
			transferParam->LastTransferred  += ret;
            transferParam->TotalTransferred += ret;
            transferParam->Packets++;
        }

        LeaveCriticalSection(&DisplayCriticalSection);
    }

Done:

	for (i=0; i < transferParam->Test->BufferCount; i++)
	{
		if (transferParam->TransferHandles[i].Context)
		{
			if (transferParam->TransferHandles[i].InUse)
			{
				if ((ret = usb_cancel_async(transferParam->TransferHandles[i].Context)) < 0)
					if (!transferParam->Test->IsUserAborted)
						CONERR("failed cancelling transfer! ret=%d\n",ret);

				transferParam->TransferHandles[i].InUse=FALSE;
			}
			usb_free_async(&transferParam->TransferHandles[i].Context);
		}
	}

    transferParam->IsRunning = FALSE;
    return 0;
}

char* GetParamStrValue(const char* src, const char* paramName)
{
	return (strstr(src,paramName)==src) ? (char*)(src+strlen(paramName)) : NULL;
}

BOOL GetParamIntValue(const char* src, const char* paramName, INT* returnValue)
{
    char* value = GetParamStrValue(src, paramName);
    if (value)
    {
        *returnValue = strtol(value, NULL, 0);
        return TRUE;
    }
    return FALSE;
}

int ValidateBenchmarkArgs(struct BENCHMARK_TEST_PARAM* testParam)
{
    if (testParam->BufferCount < 1 || testParam->BufferCount > MAX_OUTSTANDING_TRANSFERS)
    {
		CONERR("Invalid BufferCount argument %d. BufferCount must be greater than 0 and less than or equal to %d.\n",
			testParam->BufferCount, MAX_OUTSTANDING_TRANSFERS);
        return -1;
    }

    return 0;
}

int ParseBenchmarkArgs(struct BENCHMARK_TEST_PARAM* testParams, int argc, char **argv)
{
#define GET_INT_VAL
    char arg[128];
    char* value;
    int iarg;

    for (iarg=1; iarg < argc; iarg++)
    {
		if (strcpy_s(arg, _countof(arg), argv[iarg])!=ERROR_SUCCESS)
			return -1;

        strlwr(arg);

        if      (GetParamIntValue(arg, "vid=", &testParams->Vid)) {}
        else if (GetParamIntValue(arg, "pid=", &testParams->Pid)) {}
        else if (GetParamIntValue(arg, "retry=", &testParams->Retry)) {}
        else if (GetParamIntValue(arg, "buffercount=", &testParams->BufferCount)) 
		{
			if (testParams->BufferCount > 1)
				testParams->TransferMode = TRANSFER_MODE_ASYNC;
		}
        else if (GetParamIntValue(arg, "buffersize=", &testParams->BufferSize)) {}
        else if (GetParamIntValue(arg, "size=", &testParams->BufferSize)) {}
        else if (GetParamIntValue(arg, "timeout=", &testParams->Timeout)) {}
        else if (GetParamIntValue(arg, "intf=", &testParams->Intf)) {}
        else if (GetParamIntValue(arg, "altf=", &testParams->Altf)) {}
        else if (GetParamIntValue(arg, "ep=", &testParams->Ep)) 
		{
			testParams->Ep &= 0xf;
		}
        else if (GetParamIntValue(arg, "refresh=", &testParams->Refresh)) {}
        else if (GetParamIntValue(arg, "isopacketsize=", &testParams->IsoPacketSize)) {}
        else if ((value=GetParamStrValue(arg,"mode=")))
        {
            if (GetParamStrValue(value,"sync"))
            {
				testParams->TransferMode = TRANSFER_MODE_SYNC;
            }
            else if (GetParamStrValue(value,"async"))
            {
				testParams->TransferMode = TRANSFER_MODE_ASYNC;
            }
             else
            {
                // Invalid EndpointType argument.
                CONERR("invalid transfer mode argument! %s\n",argv[iarg]);
                return -1;

            }
        }
        else if ((value=GetParamStrValue(arg,"priority=")))
        {
            if (GetParamStrValue(value,"lowest"))
            {
                testParams->Priority=THREAD_PRIORITY_LOWEST;
            }
            else if (GetParamStrValue(value,"belownormal"))
            {
                testParams->Priority=THREAD_PRIORITY_BELOW_NORMAL;
            }
            else if (GetParamStrValue(value,"normal"))
            {
                testParams->Priority=THREAD_PRIORITY_NORMAL;
            }
            else if (GetParamStrValue(value,"abovenormal"))
            {
                testParams->Priority=THREAD_PRIORITY_ABOVE_NORMAL;
            }
            else if (GetParamStrValue(value,"highest"))
            {
                testParams->Priority=THREAD_PRIORITY_HIGHEST;
            }
            else
            {
                CONERR("invalid priority argument! %s\n",argv[iarg]);
                return -1;
            }
        }
        else if (!stricmp(arg,"notestselect"))
        {
            testParams->NoTestSelect = TRUE;
        }
        else if (!stricmp(arg,"read"))
        {
            testParams->TestType = TestTypeRead;
        }
        else if (!stricmp(arg,"write"))
        {
            testParams->TestType = TestTypeWrite;
        }
        else if (!stricmp(arg,"loop"))
        {
            testParams->TestType = TestTypeLoop;
        }
        else if (!stricmp(arg,"list"))
        {
            testParams->UseList = TRUE;
        }
        else if (!stricmp(arg,"verifydetails"))
        {
            testParams->VerifyDetails = TRUE;
            testParams->Verify = TRUE;
        }
        else if (!stricmp(arg,"verify"))
        {
            testParams->Verify = TRUE;
        }
		else
        {
            CONERR("invalid argument! %s\n",argv[iarg]);
            return -1;
        }
    }
    return ValidateBenchmarkArgs(testParams);
}

INT CreateVerifyBuffer(struct BENCHMARK_TEST_PARAM* testParam, WORD endpointMaxPacketSize)
{
	int i;
	BYTE indexC = 0;
	testParam->VerifyBuffer = malloc(endpointMaxPacketSize);
	if (!testParam->VerifyBuffer)
	{
        CONERR("memory allocation failure at line %d!\n",__LINE__);
        return -1;
	}

	testParam->VerifyBufferSize = endpointMaxPacketSize;

	for(i=0; i < endpointMaxPacketSize; i++)
	{
	   testParam->VerifyBuffer[i] = indexC++;
	   if (indexC == 0) indexC = 1;
	}

	return 0;
}

void FreeTransferParam(struct BENCHMARK_TRANSFER_PARAM** testTransferRef)
{
	struct BENCHMARK_TRANSFER_PARAM* pTransferParam;

	if ((!testTransferRef) || !*testTransferRef) return;
	pTransferParam = *testTransferRef;

    if (pTransferParam->ThreadHandle)
    {
        CloseHandle(pTransferParam->ThreadHandle);
        pTransferParam->ThreadHandle = NULL;
    }

    free(pTransferParam);

    *testTransferRef = NULL;
}

struct BENCHMARK_TRANSFER_PARAM* CreateTransferParam(struct BENCHMARK_TEST_PARAM* test, int endpointID)
{
    struct BENCHMARK_TRANSFER_PARAM* transferParam;
	struct usb_interface_descriptor* testInterface;
	int i;
    int allocSize = sizeof(struct BENCHMARK_TRANSFER_PARAM)+(test->BufferSize * test->BufferCount);

    transferParam = (struct BENCHMARK_TRANSFER_PARAM*) malloc(allocSize);

    if (transferParam)
    {
        memset(transferParam, 0, allocSize);
        transferParam->Test = test;
		if (!(testInterface = usb_find_interface(&test->Device->config[0], test->Intf, test->Altf, NULL)))
		{
            CONERR("failed locating interface %02Xh!\n", test->Intf);
            FreeTransferParam(&transferParam);
			goto Done;
		}

		for(i=0; i < testInterface->bNumEndpoints; i++)
		{
			if (!(endpointID & USB_ENDPOINT_ADDRESS_MASK))
			{
				// Use first endpoint that matches the direction
				if ((testInterface->endpoint[i].bEndpointAddress & USB_ENDPOINT_DIR_MASK) == endpointID)
				{
					memcpy(&transferParam->Ep, &testInterface->endpoint[i],sizeof(struct usb_endpoint_descriptor));
					break;
				}
			}
			else
			{
				if ((int)testInterface->endpoint[i].bEndpointAddress == endpointID)
				{
					memcpy(&transferParam->Ep, &testInterface->endpoint[i],sizeof(struct usb_endpoint_descriptor));
					break;
				}
			}
		}
        if (!transferParam->Ep.bEndpointAddress)
        {
            CONERR("failed locating EP%02Xh!\n", endpointID);
            FreeTransferParam(&transferParam);
			goto Done;
        }

        if (transferParam->Test->BufferSize % transferParam->Ep.wMaxPacketSize)
        {
            CONERR("buffer size %d is not an interval of EP%02Xh maximum packet size of %d!\n",
				transferParam->Test->BufferSize,
				transferParam->Ep.bEndpointAddress,
				transferParam->Ep.wMaxPacketSize);

            FreeTransferParam(&transferParam);
			goto Done;
        }

		if (test->IsoPacketSize)
			transferParam->IsoPacketSize = test->IsoPacketSize;
		else
			transferParam->IsoPacketSize = transferParam->Ep.wMaxPacketSize;

		if (ENDPOINT_TYPE(transferParam) == USB_ENDPOINT_TYPE_ISOCHRONOUS)
			transferParam->Test->TransferMode = TRANSFER_MODE_ASYNC;

        ResetRunningStatus(transferParam);

        transferParam->ThreadHandle = CreateThread(
                                          NULL,
                                          0,
                                          (LPTHREAD_START_ROUTINE)TransferThreadProc,
                                          transferParam,
                                          CREATE_SUSPENDED,
                                          &transferParam->ThreadID);

        if (!transferParam->ThreadHandle)
        {
            CONERR0("failed creating thread!\n");
            FreeTransferParam(&transferParam);
			goto Done;
        }

		// If verify mode is on, this is a loop test, and this is a write endpoint, fill
		// the buffers with the same test data sent by a benchmark device when running
		// a read only test.
		if (transferParam->Test->Verify &&
			transferParam->Test->TestType == TestTypeLoop &&
			!(transferParam->Ep.bEndpointAddress & USB_ENDPOINT_DIR_MASK))
		{
			// Data Format:
			// [0][KeyByte] 2 3 4 5 ..to.. wMaxPacketSize (if data byte rolls it is incremented to 1)
			// Increment KeyByte and repeat
			//
			BYTE indexC=0;
			INT bufferIndex = 0;
			WORD dataIndex;
			INT packetIndex;
			INT packetCount = ((transferParam->Test->BufferCount*transferParam->Test->BufferSize) / transferParam->Ep.wMaxPacketSize);
			for(packetIndex = 0; packetIndex < packetCount; packetIndex++)
			{
				indexC = 2;
				for (dataIndex=0; dataIndex < transferParam->Ep.wMaxPacketSize; dataIndex++)
				{
					if (dataIndex == 0)			// Start
						transferParam->Buffer[bufferIndex] = 0;
					else if (dataIndex == 1)	// Key
						transferParam->Buffer[bufferIndex] = packetIndex & 0xFF;
					else						// Data
						transferParam->Buffer[bufferIndex] = indexC++;

					// if wMaxPacketSize is > 255, indexC resets to 1.
					if (indexC == 0) indexC = 1;

					bufferIndex++;
				}
			}
		}
    }

Done:
    if (!transferParam)
        CONERR0("failed creating transfer param!\n");

    return transferParam;
}

void GetAverageBytesSec(struct BENCHMARK_TRANSFER_PARAM* transferParam, DOUBLE* bps)
{
	DOUBLE ticksSec;
    if ((!transferParam->StartTick) || 
		(transferParam->StartTick >= transferParam->LastTick) || 
		transferParam->TotalTransferred==0)
    {
        *bps=0;
    }
    else
    {
		ticksSec = (transferParam->LastTick - transferParam->StartTick) / 1000.0;
		*bps = (transferParam->TotalTransferred / ticksSec);
    }
}

void GetCurrentBytesSec(struct BENCHMARK_TRANSFER_PARAM* transferParam, DOUBLE* bps)
{
	DOUBLE ticksSec;
    if ((!transferParam->StartTick) || 
		(!transferParam->LastStartTick) || 
		(transferParam->LastTick <= transferParam->LastStartTick) || 
		transferParam->LastTransferred==0)
    {
        *bps=0;
    }
    else
    {
		ticksSec = (transferParam->LastTick - transferParam->LastStartTick) / 1000.0;
		*bps = transferParam->LastTransferred / ticksSec;
    }
}

void ShowRunningStatus(struct BENCHMARK_TRANSFER_PARAM* transferParam)
{
	struct BENCHMARK_TRANSFER_PARAM temp;
	DOUBLE bpsOverall;
	DOUBLE bpsLastTransfer;
    
	// LOCK the display critical section
    EnterCriticalSection(&DisplayCriticalSection);
	
	memcpy(&temp, transferParam, sizeof(struct BENCHMARK_TRANSFER_PARAM));
	
	// UNLOCK the display critical section
    LeaveCriticalSection(&DisplayCriticalSection);

    if ((!temp.StartTick) || (temp.StartTick >= temp.LastTick))
    {
        CONMSG("Synchronizing %d..\n", abs(transferParam->Packets));
    }
    else
    {
        GetAverageBytesSec(&temp,&bpsOverall);
        GetCurrentBytesSec(&temp,&bpsLastTransfer);
		transferParam->LastStartTick = 0;
		CONMSG("Avg. Bytes/s: %.2f Transfers: %d Bytes/s: %.2f\n",
			bpsOverall, temp.Packets, bpsLastTransfer);
    }

}
void ShowTransferInfo(struct BENCHMARK_TRANSFER_PARAM* transferParam)
{
    DOUBLE bpsAverage;
    DOUBLE bpsCurrent;
    DOUBLE elapsedSeconds;

	if (!transferParam) return;

	CONMSG("%s %s (Ep%02Xh) max packet size: %d\n",
		EndpointTypeDisplayString[ENDPOINT_TYPE(transferParam)],
		TRANSFER_DISPLAY(transferParam,"Read","Write"),
		transferParam->Ep.bEndpointAddress,
		transferParam->Ep.wMaxPacketSize);

	if (transferParam->StartTick)
    {
        GetAverageBytesSec(transferParam,&bpsAverage);
        GetCurrentBytesSec(transferParam,&bpsCurrent);
        CONMSG("\tTotal Bytes     : %I64d\n", transferParam->TotalTransferred);
        CONMSG("\tTotal Transfers : %d\n", transferParam->Packets);

		if (transferParam->ShortTransferCount)
		{
			CONMSG("\tShort Transfers : %d\n", transferParam->ShortTransferCount);
		}
		if (transferParam->TotalTimeoutCount)
		{
			CONMSG("\tTimeout Errors  : %d\n", transferParam->TotalTimeoutCount);
		}
		if (transferParam->TotalErrorCount)
		{
			CONMSG("\tOther Errors    : %d\n", transferParam->TotalErrorCount);
		}

        CONMSG("\tAvg. Bytes/sec  : %.2f\n", bpsAverage);

		if (transferParam->StartTick && transferParam->StartTick < transferParam->LastTick)
		{
			elapsedSeconds = (transferParam->LastTick - transferParam->StartTick) / 1000.0;

			CONMSG("\tElapsed Time    : %.2f seconds\n", elapsedSeconds);
		}

	    CONMSG0("\n");
    }

}

void ShowTestInfo(struct BENCHMARK_TEST_PARAM* testParam)
{
    if (!testParam) return;

    CONMSG("%s Test Information\n",TestDisplayString[testParam->TestType & 3]);
    CONMSG("\tVid / Pid       : %04Xh / %04Xh\n", testParam->Vid,  testParam->Pid);
    CONMSG("\tInterface #     : %02Xh\n", testParam->Intf);
    CONMSG("\tPriority        : %d\n", testParam->Priority);
    CONMSG("\tBuffer Size     : %d\n", testParam->BufferSize);
    CONMSG("\tBuffer Count    : %d\n", testParam->BufferCount);
    CONMSG("\tDisplay Refresh : %d (ms)\n", testParam->Refresh);
    CONMSG("\tTransfer Timeout: %d (ms)\n", testParam->Timeout);
    CONMSG("\tRetry Count     : %d\n", testParam->Retry);
    CONMSG("\tVerify Data     : %s%s\n",
		testParam->Verify ? "On" : "Off",
		(testParam->Verify && testParam->VerifyDetails) ? " (Detailed)" : "");

    CONMSG0("\n");
}

void WaitForTestTransfer(struct BENCHMARK_TRANSFER_PARAM* transferParam)
{
    DWORD exitCode;
    while (transferParam)
    {
        if (!transferParam->IsRunning)
        {
            if (GetExitCodeThread(transferParam->ThreadHandle, &exitCode))
            {
                if (exitCode == 0)
                {
                    CONMSG("stopped Ep%02Xh thread.\tExitCode=%d\n",
                              transferParam->Ep.bEndpointAddress, exitCode);
                    break;
                }
            }
            else
            {
                CONERR("failed getting Ep%02Xh thread exit code!\n",transferParam->Ep.bEndpointAddress);
                break;
            }
        }
        Sleep(100);
        CONMSG("waiting for Ep%02Xh thread..\n", transferParam->Ep.bEndpointAddress);
    }
}
void ResetRunningStatus(struct BENCHMARK_TRANSFER_PARAM* transferParam)
{
    if (!transferParam) return;

    transferParam->StartTick=0;
    transferParam->TotalTransferred=0;
    transferParam->Packets=-2;
    transferParam->LastTick=0;
    transferParam->RunningTimeoutCount=0;
}

int GetTestDeviceFromList(struct BENCHMARK_TEST_PARAM* testParam)
{
    const int LINE_MAX_SIZE   = 1024;
    const int STRING_MAX_SIZE = 256;
    const int NUM_STRINGS = 3;
    const int ALLOC_SIZE = LINE_MAX_SIZE + (STRING_MAX_SIZE * NUM_STRINGS);

    int userInput;

    char* buffer;
    char* line;
    char* product;
    char* manufacturer;
    char* serial;

    struct usb_bus* bus;
    struct usb_device* dev;
    usb_dev_handle* udev;
    struct usb_device* validDevices[256];

    int deviceIndex=0;
	struct usb_interface_descriptor* firstInterface;

    int ret = -1;

    buffer = malloc(ALLOC_SIZE);
    if (!buffer)
    {
        CONERR0("failed allocating memory!\n");
        return ret;
    }

    line = buffer;
    product = buffer + LINE_MAX_SIZE;
    manufacturer = product + STRING_MAX_SIZE;
    serial = manufacturer + STRING_MAX_SIZE;

    for (bus = usb_get_busses(); bus; bus = bus->next)
    {
        for (dev = bus->devices; dev; dev = dev->next)
        {

            udev = usb_open(dev);
            if (udev)
            {
                memset(buffer, 0, ALLOC_SIZE);
                line = buffer;
                if (dev->descriptor.iManufacturer)
                {
                    if (usb_get_string_simple(udev, dev->descriptor.iManufacturer, manufacturer, STRING_MAX_SIZE - 1) > 0)
                    {
                        strcat(line,"(");
                        strcat(line,manufacturer);
                        strcat(line,") ");
                    }
                }

                if (dev->descriptor.iProduct)
                {
                    if (usb_get_string_simple(udev, dev->descriptor.iProduct, product, STRING_MAX_SIZE - 1) > 0)
                    {
                        strcat(line,product);
                        strcat(line," ");
                    }
                }

                if (dev->descriptor.iSerialNumber)
                {
                    if (usb_get_string_simple(udev, dev->descriptor.iSerialNumber, serial, STRING_MAX_SIZE - 1) > 0)
                    {
                        strcat(line,"[");
                        strcat(line,serial);
                        strcat(line,"] ");
                    }
                }

                if (!deviceIndex)
                    CONMSG0("\n");

                validDevices[deviceIndex++] = dev;

                CONMSG("%d. %04X:%04X %s\n",
                          deviceIndex, dev->descriptor.idVendor, dev->descriptor.idProduct, line);

                usb_close(udev);
            }
        }
    }
    
	if (!deviceIndex) 
	{
		CONERR0("No devices where found!\n");
		ret = -1;
		goto Done;
	}

    CONMSG("\nSelect device (1-%d) :",deviceIndex);
	ret = _cscanf("%i",&userInput);
    if (ret != 1 || userInput < 1)
	{
        CONMSG0("\n");
        CONMSG0("Aborting..\n");
		ret = -1;
		goto Done;
	}
    CONMSG0("\n");
    userInput--;
    if (userInput >= 0 && userInput < deviceIndex)
    {
        testParam->DeviceHandle = usb_open(validDevices[userInput]);
        if (testParam->DeviceHandle)
        {
			testParam->Device = validDevices[userInput];
            testParam->Vid = testParam->Device->descriptor.idVendor;
            testParam->Pid = testParam->Device->descriptor.idProduct;
			if (usb_find_interface(&validDevices[userInput]->config[0],testParam->Intf, testParam->Altf, &firstInterface) == NULL)
			{
				// the specified (or default) interface didn't exist, use the first one.
				if (firstInterface != NULL)
				{
					testParam->Intf = firstInterface->bInterfaceNumber;
				}
				else
				{
					CONERR("device %04X:%04X does not have any interfaces!\n",
						testParam->Vid, testParam->Pid);
					ret = -1;
					goto Done;
				}
			}
            ret = 0;
        }
	}

Done:
	if (buffer)
        free(buffer);

    return ret;
}

int main(int argc, char** argv)
{
    struct BENCHMARK_TEST_PARAM Test;
    struct BENCHMARK_TRANSFER_PARAM* ReadTest	= NULL;
    struct BENCHMARK_TRANSFER_PARAM* WriteTest	= NULL;
    int key;


    if (argc == 1)
    {
        ShowHelp();
        return -1;
    }

	ShowCopyright();

    // NOTE: This is the log level for the benchmark application.
    //
#if defined __ERROR_H__
    usb_log_set_level(255);
#endif

    SetTestDefaults(&Test);

    // Load the command line arguments.
    if (ParseBenchmarkArgs(&Test, argc, argv) < 0)
        return -1;

    // Initialize the critical section used for locking
    // the volatile members of the transfer params in order
    // to update/modify the running statistics.
    //
    InitializeCriticalSection(&DisplayCriticalSection);

    // Initialize the library.
    usb_init();

    // Find all busses.
    usb_find_busses();

    // Find all connected devices.
    usb_find_devices();

    if (Test.UseList)
    {
        if (GetTestDeviceFromList(&Test) < 0)
            goto Done;
    }
    else
    {
        // Open a benchmark device. see Bench_Open().
        Test.DeviceHandle = Bench_Open(Test.Vid, Test.Pid, Test.Intf, Test.Altf, &Test.Device);
    }
    if (!Test.DeviceHandle || !Test.Device)
    {
        CONERR("device %04X:%04X not found!\n",Test.Vid, Test.Pid);
        goto Done;
    }

    // If "NoTestSelect" appears in the command line then don't send the control
    // messages for selecting the test type.
    //
    if (!Test.NoTestSelect)
    {
        if (Bench_SetTestType(Test.DeviceHandle, Test.TestType, Test.Intf) != 1)
        {
            CONERR("setting bechmark test type #%d!\n%s\n", Test.TestType, usb_strerror());
            goto Done;
        }
    }

    CONMSG("Benchmark device %04X:%04X opened..\n",Test.Vid, Test.Pid);

    // If reading from the device create the read transfer param. This will also create
    // a thread in a suspended state.
    //
    if (Test.TestType & TestTypeRead)
    {
        ReadTest = CreateTransferParam(&Test, Test.Ep | USB_ENDPOINT_DIR_MASK);
        if (!ReadTest) goto Done;
    }

    // If writing to the device create the write transfer param. This will also create
    // a thread in a suspended state.
    //
    if (Test.TestType & TestTypeWrite)
    {
        WriteTest = CreateTransferParam(&Test, Test.Ep);
        if (!WriteTest) goto Done;
    }

    // Set configuration #1.
    if (usb_set_configuration(Test.DeviceHandle, 1) < 0)
    {
        CONERR("setting configuration #%d!\n%s\n",1,usb_strerror());
        goto Done;
    }

    // Claim_interface Test.Intf (Default is #0)
    if (usb_claim_interface(Test.DeviceHandle, Test.Intf) < 0)
    {
        CONERR("claiming interface #%d!\n%s\n", Test.Intf, usb_strerror());
        goto Done;
    }

    // Set the alternate setting (Default is #0)
	if (usb_set_altinterface(Test.DeviceHandle, Test.Altf) < 0)
	{
		CONERR("selecting alternate setting #%d on interface #%d!\n%s\n", Test.Altf,  Test.Intf, usb_strerror());
        goto Done;
	}
	else
	{
		if (Test.Altf > 0)
		{
			CONDBG("selected alternate setting #%d on interface #%d\n",Test.Altf,  Test.Intf);
		}
	}

	if (Test.Verify)
	{
		if (ReadTest && WriteTest)
		{
			if (CreateVerifyBuffer(&Test, WriteTest->Ep.wMaxPacketSize) < 0)
				goto Done;
		}
		else if (ReadTest)
		{
			if (CreateVerifyBuffer(&Test, ReadTest->Ep.wMaxPacketSize) < 0)
				goto Done;
		}
	}

	ShowTestInfo(&Test);
	ShowTransferInfo(ReadTest);
	ShowTransferInfo(WriteTest);

	CONMSG0("\nWhile the test is running:\n");
	CONMSG0("Press 'Q' to quit\n");
	CONMSG0("Press 'T' for test details\n");
	CONMSG0("Press 'I' for status information\n");
	CONMSG0("Press 'R' to reset averages\n");
    CONMSG0("\nPress 'Q' to exit, any other key to begin..");
    key = _getch();
    CONMSG0("\n");

    if (key=='Q' || key=='q') goto Done;

    // Set the thread priority and start it.
    if (ReadTest)
    {
        SetThreadPriority(ReadTest->ThreadHandle, Test.Priority);
        ResumeThread(ReadTest->ThreadHandle);
    }

    // Set the thread priority and start it.
    if (WriteTest)
    {
        SetThreadPriority(WriteTest->ThreadHandle, Test.Priority);
        ResumeThread(WriteTest->ThreadHandle);
    }

    while (!Test.IsCancelled)
    {
        Sleep(Test.Refresh);

        if (_kbhit())
        {
            // A key was pressed.
            key = _getch();
            switch (key)
            {
            case 'Q':
            case 'q':
                Test.IsUserAborted = TRUE;
                Test.IsCancelled = TRUE;
                break;
			case 'T':
			case 't':
				ShowTestInfo(&Test);
				break;
            case 'I':
            case 'i':
                // LOCK the display critical section
                EnterCriticalSection(&DisplayCriticalSection);

                // Print benchmark test details.
				ShowTransferInfo(ReadTest);
				ShowTransferInfo(WriteTest);


                // UNLOCK the display critical section
                LeaveCriticalSection(&DisplayCriticalSection);
                break;

            case 'R':
            case 'r':
                // LOCK the display critical section
                EnterCriticalSection(&DisplayCriticalSection);

                // Reset the running status.
                ResetRunningStatus(ReadTest);
                ResetRunningStatus(WriteTest);

                // UNLOCK the display critical section
                LeaveCriticalSection(&DisplayCriticalSection);
                break;
            }

            // Only one key at a time.
            while (_kbhit()) _getch();
        }

        // If the read test should be running and it isn't, cancel the test.
        if ((ReadTest) && !ReadTest->IsRunning)
        {
            Test.IsCancelled = TRUE;
            break;
        }

        // If the write test should be running and it isn't, cancel the test.
        if ((WriteTest) && !WriteTest->IsRunning)
        {
            Test.IsCancelled = TRUE;
            break;
        }

        // Print benchmark stats
        if (ReadTest)
            ShowRunningStatus(ReadTest);
        else
            ShowRunningStatus(WriteTest);

    }

	// Wait for the transfer threads to complete gracefully if it
	// can be done in 10ms. All of the code from this point to
	// WaitForTestTransfer() is not required.  It is here only to
	// improve response time when the test is cancelled.
	//
    Sleep(10);

	// If the thread is still running, abort and reset the endpoint.
    if ((ReadTest) && ReadTest->IsRunning)
        usb_resetep(Test.DeviceHandle, ReadTest->Ep.bEndpointAddress);

    // If the thread is still running, abort and reset the endpoint.
    if ((WriteTest) && WriteTest->IsRunning)
        usb_resetep(Test.DeviceHandle, WriteTest->Ep.bEndpointAddress);

    // Small delay incase usb_resetep() was called.
    Sleep(10);

    // WaitForTestTransfer will not return until the thread
	// has exited.
    WaitForTestTransfer(ReadTest);
    WaitForTestTransfer(WriteTest);

    // Print benchmark detailed stats
	ShowTestInfo(&Test);
	if (ReadTest) ShowTransferInfo(ReadTest);
	if (WriteTest) ShowTransferInfo(WriteTest);


Done:
    if (Test.DeviceHandle)
    {
        usb_close(Test.DeviceHandle);
        Test.DeviceHandle = NULL;
    }
	if (Test.VerifyBuffer)
	{
		free(Test.VerifyBuffer);
		Test.VerifyBuffer = NULL;

	}
    FreeTransferParam(&ReadTest);
    FreeTransferParam(&WriteTest);

    DeleteCriticalSection(&DisplayCriticalSection);

    CONMSG0("Press any key to exit..");
    _getch();
    CONMSG0("\n");

    return 0;
}

//////////////////////////////////////////////////////////////////////////////
/* END OF PROGRAM                                                           */
//////////////////////////////////////////////////////////////////////////////
void ShowHelp(void)
{
	#define ID_HELP_TEXT  10020
	#define ID_DOS_TEXT   300

	CONST CHAR* src;
	DWORD src_count, charsWritten;
	HGLOBAL res_data;
	HANDLE handle;
	HRSRC hSrc;

	ShowCopyright();

	hSrc = FindResourceA(NULL, MAKEINTRESOURCEA(ID_HELP_TEXT),MAKEINTRESOURCEA(ID_DOS_TEXT));
	if (!hSrc)	return;

	src_count = SizeofResource(NULL, hSrc);

	res_data = LoadResource(NULL, hSrc);
	if (!res_data)	return;

	src = (char*) LockResource(res_data);
	if (!src) return;

	if ((handle=GetStdHandle(STD_ERROR_HANDLE)) != INVALID_HANDLE_VALUE)
		WriteConsoleA(handle, src, src_count, &charsWritten, NULL);
}

void ShowCopyright(void)
{
	CONMSG0("libusb-win32 USB Benchmark v" RC_VERSION_STR "\n");
	CONMSG0("Copyright (c) 2010 Travis Robinson. <libusbdotnet@gmail.com>\n");
	CONMSG0("website: http://sourceforge.net/projects/libusbdotnet\n");
}
