// Copyright (c) 2020 Metrolab Technology S.A., Geneva, Switzerland (www.metrolab.com)
// See the included file LICENSE.txt for the licensing conditions.

//////////////////////////////////////////////////////////////////////////
/// \file
/// \brief USBTMC driver based on libusb: implementation.

// Standard includes
#include <string>
#include <cstring>
#include <regex>
#include <map>
#include <chrono>
#include <thread>

// Personal includes
#include "USBTMCInstrument.h"
#include "USBTMCPrivate.h"
#include "OSDefines.h"
#include "Helpers.h"

// Definitions
#define DEBUG_MTL_USBTMC_INSTRUMENT					0
#define DEBUG_MTL_USBTMC_INSTRUMENT_ERRORS_ONLY		0
#if (defined(_DEBUG) && defined(DEBUG_MTL_USBTMC_INSTRUMENT) && DEBUG_MTL_USBTMC_INSTRUMENT)
	#if (defined(DEBUG_MTL_USBTMC_INSTRUMENT_ERRORS_ONLY) && DEBUG_MTL_USBTMC_INSTRUMENT_ERRORS_ONLY)
		#define MTL_USBTMC_INSTRUMENT_DEBUG_COUT(__X__)
	#else
		#define MTL_USBTMC_INSTRUMENT_DEBUG_COUT(__X__)		COUT(__X__)
	#endif
	#define MTL_USBTMC_INSTRUMENT_DEBUG_CERR(__X__)		CERR(__X__)
#else
	#define MTL_USBTMC_INSTRUMENT_DEBUG_COUT(__X__)
	#define MTL_USBTMC_INSTRUMENT_DEBUG_CERR(__X__)
#endif
#define MTL_USBTMC_PAUSE_BETWEEN_READS              2  // ms

using namespace MTL::Instrument;

//----------------------------------------------------------------------//
//							Resource Manager							//
//----------------------------------------------------------------------//
//------------------------------------------//
// Constructors / destructors
CUSBTMCResourceManager::CUSBTMCResourceManager(void) :
    m_pContext(nullptr)
{

} // CUSBTMCResourceManager::CUSBTMCResourceManager

CUSBTMCResourceManager::~CUSBTMCResourceManager(void)
{
	// Close the devices on the device list.
	for (auto l_pDeviceListEntry = m_DeviceList.begin(); l_pDeviceListEntry != m_DeviceList.end(); l_pDeviceListEntry++)
	{
		if (l_pDeviceListEntry->pDevice != nullptr)
			libusb_unref_device(l_pDeviceListEntry->pDevice);
	}
	m_DeviceList.clear();

	// Shut down libusb.
    if (m_pContext != nullptr)
        libusb_exit(m_pContext);
	
} // CUSBTMCResourceManager::~CUSBTMCResourceManager

//------------------------------------------//
// Initialization
bool CUSBTMCResourceManager::Initialize(void)
{
	MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CMutex> l_LockGuard(m_Lock);

	try
	{
		// Initialize libusb.
        m_Status = libusb_init (&m_pContext);
		if (m_Status != LIBUSB_SUCCESS)
			throw Exception(m_Status, MTL__LOCATION__);

		// Set the log level.
        m_Status = libusb_set_option (m_pContext, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_WARNING);
		if (m_Status != LIBUSB_SUCCESS)
			throw Exception(m_Status, MTL__LOCATION__);
	}
	
	// Handle errors.
	catch (MTL::CException<CUSBTMCResourceManager> & rE)
	{
        MTL_Unused(rE)
		MTL_USBTMC_INSTRUMENT_DEBUG_CERR(rE.what());
        if (m_pContext != nullptr)
            libusb_exit	(m_pContext);
		return false;
	}
	
	return true;
	
} // CUSBTMCResourceManager::Initialize

//------------------------------------------//
// Find resources
bool CUSBTMCResourceManager::FindResources(CResourceList & rList, std::string Filter)
{
	MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CMutex> l_LockGuard(m_Lock);

	libusb_device **					l_USBDeviceList	= nullptr;
    libusb_device_handle *				l_hDevice		= nullptr;
	tUSBTMCDeviceListEntry				l_NewDeviceListEntry;
	l_NewDeviceListEntry.clear();

	try
	{
		// Clear the resource lists.
		rList.clear();
		for (auto l_pDeviceListEntry = m_DeviceList.begin(); l_pDeviceListEntry != m_DeviceList.end(); l_pDeviceListEntry++)
			l_pDeviceListEntry->PluggedIn = false;
		
		// Retrieve the Vendor ID and Product ID from the Filter.
		const std::regex l_RegexVID_PID("([0-9]+):([0-9]+)");
		std::smatch l_MatchVID_PID;
		if (!std::regex_match(Filter, l_MatchVID_PID, l_RegexVID_PID) ||
			l_MatchVID_PID.size() != 3)
			throw Exception(USBTMC_ERROR_FIND_RESRC_BAD_FILTER, MTL__LOCATION__);
		
        U16 l_VID = static_cast<U16>(stoi(l_MatchVID_PID[1].str()));
        U16 l_PID = static_cast<U16>(stoi(l_MatchVID_PID[2].str()));

		// List connected USB devices.
        ssize_t l_DeviceCount = libusb_get_device_list(nullptr, &l_USBDeviceList);
		if (l_DeviceCount < 0)
			throw Exception(static_cast<I32>(l_DeviceCount), MTL__LOCATION__);
		
		// Loop through the devices to check whether any of them match VID:PID.
		for (ssize_t i = 0; i < l_DeviceCount; i++)
		{
			// Clear the device pointer, in case of exception.
			l_NewDeviceListEntry.clear();
			
			// Get the device descriptor of this device.
			libusb_device *					l_pUSBDevice = l_USBDeviceList[i];
			struct libusb_device_descriptor	l_USBDeviceDescriptor;
			m_Status = libusb_get_device_descriptor(l_pUSBDevice, &l_USBDeviceDescriptor);
			if (m_Status != LIBUSB_SUCCESS)
				throw Exception(m_Status, MTL__LOCATION__);

			// Bail if this descriptor does not match VID:PID.
			if (l_USBDeviceDescriptor.idVendor	!= l_VID ||
				l_USBDeviceDescriptor.idProduct	!= l_PID)
				continue;
			
			// Get the port-path for this device.
			U8 l_PortNumbers[USB_MAX_PORT_DEPTH];
			I32 l_PathLength = libusb_get_port_numbers(l_pUSBDevice, l_PortNumbers, USB_MAX_PORT_DEPTH);
			if (l_PathLength < 0)
				throw Exception(l_PathLength, MTL__LOCATION__);
			std::vector<U8> l_PortPath;
			for (I32 i = 0; i < l_PathLength; i++)
				l_PortPath.push_back(l_PortNumbers[i]);
			
			// If this device is already in the device list, copy the resource name to the output,
			// mark the device as plugged in, and bail.
			auto l_pDeviceListEntry = m_DeviceList.begin();
			for (; l_pDeviceListEntry != m_DeviceList.end(); l_pDeviceListEntry++)
				if (l_PortPath == l_pDeviceListEntry->PortPath) break;
			if (l_pDeviceListEntry != m_DeviceList.end())
			{
				rList.push_back(l_pDeviceListEntry->ResourceName);
				l_pDeviceListEntry->PluggedIn = true;
				continue;
			}

			// Get the handle for this device.
            // This could fail, for example, if the wrong driver is assigned.
            m_Status = libusb_open(l_pUSBDevice, &l_hDevice);
			if (m_Status != LIBUSB_SUCCESS)
                continue;

            // Set up the new device list.
            libusb_ref_device(l_pUSBDevice);
            l_NewDeviceListEntry.pDevice	= l_pUSBDevice;
            l_NewDeviceListEntry.PortPath   = l_PortPath;
            l_NewDeviceListEntry.PluggedIn	= true;

            // Fetch the manufacturer, product and serial number.
			U8 l_String[USB_DESCRIPTOR_STRING_LENGTH];
			I32 l_StringLength;
            l_StringLength = libusb_get_string_descriptor_ascii (l_hDevice, l_USBDeviceDescriptor.iManufacturer, l_String, USB_DESCRIPTOR_STRING_LENGTH);
			if (l_StringLength < 0)
				throw Exception(l_StringLength, MTL__LOCATION__);
			std::string l_Manufacturer = std::string(reinterpret_cast<char *>(l_String), static_cast<size_t>(l_StringLength));
			
            l_StringLength = libusb_get_string_descriptor_ascii	(l_hDevice, l_USBDeviceDescriptor.iProduct, l_String, USB_DESCRIPTOR_STRING_LENGTH);
			if (l_StringLength < 0)
				throw Exception(l_StringLength, MTL__LOCATION__);
			std::string l_Product = std::string(reinterpret_cast<char *>(l_String), static_cast<size_t>(l_StringLength));
			
            l_StringLength = libusb_get_string_descriptor_ascii	(l_hDevice, l_USBDeviceDescriptor.iSerialNumber, l_String, USB_DESCRIPTOR_STRING_LENGTH);
			if (l_StringLength < 0)
				throw Exception(l_StringLength, MTL__LOCATION__);
			std::string l_SerialNumber = std::string(reinterpret_cast<char *>(l_String), static_cast<size_t>(l_StringLength));
			
			l_NewDeviceListEntry.ResourceName = l_Manufacturer + " " + l_Product + " " + l_SerialNumber;

			// Close the device.
            libusb_close(l_hDevice);
            l_hDevice = nullptr;
			
			// Add the device to the list.
			m_DeviceList.push_back(l_NewDeviceListEntry);
			rList.push_back(l_NewDeviceListEntry.ResourceName);
			
		} // Loop through the devices

		// Free the list of connected USB devices, unreferencing the devices.
		libusb_free_device_list(l_USBDeviceList, 1);
		
		// Delete Device List entries for devices that are no longer connected.
		for (auto l_pDeviceListEntry = m_DeviceList.begin(); l_pDeviceListEntry != m_DeviceList.end();)
		{
			if (!l_pDeviceListEntry->PluggedIn)
			{
				libusb_unref_device(l_pDeviceListEntry->pDevice);
				l_pDeviceListEntry = m_DeviceList.erase(l_pDeviceListEntry);
			}
			else
			{
				++l_pDeviceListEntry;
			}
		} // Loop to delete device list entries of devices that have been unplugged.

        // Set an error status if the device list is empty.
        if (rList.size() == 0)
            m_Status = LIBUSB_ERROR_NO_DEVICE;

	} // try
	
	// Handle errors. Deallocate all the resources we may have allocated.
	catch (MTL::CException<CUSBTMCResourceManager> & rE)
	{
        MTL_Unused(rE)
		MTL_USBTMC_INSTRUMENT_DEBUG_CERR(rE.what());
		
        if (l_USBDeviceList != nullptr)
			libusb_free_device_list(l_USBDeviceList, 1);
        if (l_hDevice != nullptr)
            libusb_close(l_hDevice);
		if (l_NewDeviceListEntry.pDevice != nullptr)
			libusb_unref_device(l_NewDeviceListEntry.pDevice);
		rList.clear();
		for (auto l_pDeviceListEntry = m_DeviceList.begin(); l_pDeviceListEntry != m_DeviceList.end(); l_pDeviceListEntry++)
		{
			libusb_unref_device(l_pDeviceListEntry->pDevice);
		}
		m_DeviceList.clear();

		return false;
	}
	
	return true;
	
} // CUSBTMCResourceManager::FindResources

// Retrieve a device list entry from a previous call to FindResources.
bool CUSBTMCResourceManager::GetDeviceListEntry(const tResourceName ResourceName, libusb_device * & pDevice)
{
	MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CMutex> l_LockGuard(m_Lock);
	
	// Search the list for the given resource name.
	tUSBTMCDeviceListEntry * l_pDeviceListEntry = nullptr;
	for (auto l_pDeviceList = m_DeviceList.begin(); l_pDeviceList != m_DeviceList.end(); l_pDeviceList++)
	{
		if (l_pDeviceList->ResourceName == ResourceName)
		{
			l_pDeviceListEntry = &(*l_pDeviceList);
			break;
		}
	}
	
	// If not found, return an error.
	if (l_pDeviceListEntry == nullptr)
	{
		m_Status = USBTMC_ERROR_GET_RESRC_NOT_FOUND;
		return false;
	}
	
	// Return the device pointer.
	// Note: must increment the reference count here for thread safety: this is the only place
	// where CUSBTMCResourceManager and CUSBTMCInstrument are both blocked.
	pDevice = l_pDeviceListEntry->pDevice;
	libusb_ref_device(pDevice);

	return true;
	
} // CUSBTMCResourceManager::GetDeviceListEntry

//------------------------------------------//
// Info
std::string CUSBTMCResourceManager::StatusDescription (I32 Status)
{
	MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	
	if (Status <= 0)
		return libusb_error_name(Status);
	else
		return USBTMC_ERROR_EXPLANATION[Status];
	
} // CUSBTMCResourceManager::StatusDescription

bool CUSBTMCResourceManager::Timeout(void)
{
	MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CMutex> l_LockGuard(m_Lock);
	
	return (m_Status == LIBUSB_ERROR_TIMEOUT);
	
} // CUSBTMCResourceManager::Timeout

//------------------------------------------//
// Exception
MTL::CException<CUSBTMCResourceManager> CUSBTMCResourceManager::Exception(I32 Status, std::string Location)
{
	m_Status = Status;
	return MTL::CException<CUSBTMCResourceManager>(StatusDescription(Status), Location);
}

//----------------------------------------------------------------------//
//								Instrument								//
//----------------------------------------------------------------------//
// Utility functions to build write headers and check read headers and responses.
static bool USBTMCReadSTBResponse(U8 *	pResponse,
								  U8	USBTMC_status,
								  U8	bTag,
								  U8	StatusByte)
{
	return (R8(pResponse + 0) == USBTMC_status	&&
			R8(pResponse + 1) == bTag			&&
			R8(pResponse + 2) == StatusByte);
}

static void USBTMCBulkOutHeaderWrite(U8 *	pHeader,
									 U8		MsgID,
									 U8		bTag,
									 U32	TransferSize,
									 U8		bmTransferAttributes,
									 char	TermChar)
{
	W8	(pHeader +  0, MsgID);
	W8	(pHeader +  1, bTag);
	W8	(pHeader +  2, ~bTag);
	W8	(pHeader +  3, 0);
	WL32(pHeader +  4, TransferSize);
	W8	(pHeader +  8, bmTransferAttributes);
	W8	(pHeader +  9, TermChar);
	WL16(pHeader + 10, 0);
}

static bool USBTMCBulkInHeaderRead(U8 *		pHeader,
								   U8		MsgID,
								   U8		bTag,
								   I32 &	TransferSize,
								   U8 &		bmTransferAttributes)
{
	if (R8(pHeader + 0) != MsgID ||
		R8(pHeader + 1) != bTag  ||
		R8(pHeader + 2) != (unsigned char)~bTag)
		return false;
	TransferSize			= RL32(pHeader + 4);
	bmTransferAttributes	= R8(pHeader + 8);
	
	return true;
}

//------------------------------------------//
// Utility classes for device access control.
class tControlRecord
{
public:
	std::timed_mutex	Mutex;
	U32					UseCount;
	tControlRecord(void): UseCount(1)
	{ }
	~tControlRecord(void)
	{ assert (0 == UseCount); }
};

static class: public std::map<libusb_device *, tControlRecord *>
{
private:
	CMutex	m_Lock;
public:
	bool Register(libusb_device * pDevice)
	{
		CLockGuard<CMutex> l_LockGuard(m_Lock);
		auto l_pClaimCheck = find(pDevice);
		if (l_pClaimCheck != end())
		{
			l_pClaimCheck->second->UseCount++;
		}
		else
		{
			tControlRecord * l_pNewControlRecord = new tControlRecord;
			emplace(pDevice, l_pNewControlRecord);
			libusb_ref_device(pDevice);
		}
		return true;
	}
	bool Unregister(libusb_device * pDevice)
	{
		auto l_pClaimCheck = find(pDevice);
		if (l_pClaimCheck != end())
		{
			l_pClaimCheck->second->UseCount--;
			if (0 == l_pClaimCheck->second->UseCount)
			{
				delete l_pClaimCheck->second;
				erase(l_pClaimCheck);
				libusb_unref_device(pDevice);
			}
			return true;
		}
		else
		{
			return false;
		}
	}
	bool Claim(libusb_device * pDevice, U32 Timeout = 0)
	{
		m_Lock.lock();
		auto l_pClaimCheck = find(pDevice);
		if (l_pClaimCheck == end())
			return false;
		m_Lock.unlock();
		return l_pClaimCheck->second->Mutex.try_lock_for(std::chrono::milliseconds(Timeout));
	}
	bool Release(libusb_device * pDevice)
	{
		CLockGuard<CMutex> l_LockGuard(m_Lock);
		auto l_pClaimCheck = find(pDevice);
		if (l_pClaimCheck != end())
		{
			l_pClaimCheck->second->Mutex.unlock();
			return true;
		}
		else
			return false;
	}

} l_USBTMCAccessControl;

//------------------------------------------//
// Instrument management.

// Constructors / destructors
CUSBTMCInstrument::CUSBTMCInstrument(CUSBTMCResourceManager & rRM, tResourceName Rsrc)
    :	CIEEE488Instrument(rRM, Rsrc), m_pDevice(nullptr), m_hDevice(nullptr),
		m_ConfigurationNo(0), m_InterfaceNo(0), m_InterruptEndpoint(0), m_BulkInEndpoint(0), m_BulkOutEndpoint(0),
        m_BulkInMaxPacketSize(0), m_bTag(0), m_ExclusiveLock(false), m_SRQEnable(false)
{
	
} // CUSBTMCInstrument::CUSBTMCInstrument

CUSBTMCInstrument::~CUSBTMCInstrument(void)
{
	// Make sure the device is closed.
    CUSBTMCInstrument::Close();
	
	// Make sure the lock is released.
    CUSBTMCInstrument::Unlock();

} // CUSBTMCInstrument::~CUSBTMCInstrument

//------------------------------------------//
// Connection
bool CUSBTMCInstrument::Open(void)
{
	MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
    CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	struct libusb_config_descriptor *	l_pConfig		= nullptr;

	try
	{
        // If the device is already open, we're done.
        if (IsOpen()) return true;

		// Get the device list entry.
		if (!dynamic_cast<CUSBTMCResourceManager *>(&m_rRrsrcMan)->GetDeviceListEntry(m_Rsrc, m_pDevice))
			throw Exception(USBTMC_ERROR_GET_RESRC_NOT_FOUND, MTL__LOCATION__);
		
		// Get the device descriptor of this device.
		struct libusb_device_descriptor	l_USBDeviceDescriptor;
		m_Status = libusb_get_device_descriptor(m_pDevice, &l_USBDeviceDescriptor);
		if (m_Status != LIBUSB_SUCCESS)
			throw Exception(m_Status, MTL__LOCATION__);

		// Loop through the configurations and interfaces to find the USBTMC-USB488 interface.
		bool l_FoundInterface = false;
        for (U8 l_iConf = 0; l_iConf < l_USBDeviceDescriptor.bNumConfigurations; l_iConf++)
		{
			int l_ReturnValue = libusb_get_config_descriptor(m_pDevice, l_iConf, &l_pConfig);
			if (l_ReturnValue < 0 && l_ReturnValue != LIBUSB_ERROR_NOT_FOUND)
				continue;
			
			// Loop through interfaces and check whether they are the USBTMC-USB488 interface.
			for (int l_iIntfc = 0; l_iIntfc < l_pConfig->bNumInterfaces; l_iIntfc++)
			{
				const struct libusb_interface_descriptor *	l_pIntfc = l_pConfig->interface[l_iIntfc].altsetting;
				if (l_pIntfc->bInterfaceClass    != LIBUSB_CLASS_APPLICATION ||
					l_pIntfc->bInterfaceSubClass != SUBCLASS_USBTMC ||
					l_pIntfc->bInterfaceProtocol != USBTMC_USB488)
					continue;
				m_ConfigurationNo	= l_pConfig->bConfigurationValue;
				m_InterfaceNo		= l_pIntfc->bInterfaceNumber;
				
				// Loop through the endpoints to identify BULKIN, BULKOUT, INETERRUPT.
				for (int l_iEP = 0; l_iEP < l_pIntfc->bNumEndpoints; l_iEP++)
				{
					const struct libusb_endpoint_descriptor * l_pEndpoint = &l_pIntfc->endpoint[l_iEP];
					if (l_pEndpoint->bmAttributes == LIBUSB_TRANSFER_TYPE_BULK &&
						!(l_pEndpoint->bEndpointAddress & (LIBUSB_ENDPOINT_DIR_MASK)))
					{
						m_BulkOutEndpoint = l_pEndpoint->bEndpointAddress;
					}
					if (l_pEndpoint->bmAttributes == LIBUSB_TRANSFER_TYPE_BULK &&
						l_pEndpoint->bEndpointAddress & (LIBUSB_ENDPOINT_DIR_MASK))
					{
						m_BulkInEndpoint = l_pEndpoint->bEndpointAddress;
					}
					if (l_pEndpoint->bmAttributes == LIBUSB_TRANSFER_TYPE_INTERRUPT &&
						l_pEndpoint->bEndpointAddress & (LIBUSB_ENDPOINT_DIR_MASK))
					{
						m_InterruptEndpoint = l_pEndpoint->bEndpointAddress;
					}
				}
				
				// Get the MaxPacketSize for the BulkIn endpoint.
				m_BulkInMaxPacketSize = libusb_get_max_packet_size(m_pDevice, m_BulkInEndpoint);
				
				// Flag that we found the interface.
				l_FoundInterface = true;
				
			} // Loop through interfaces
			
			// Free the configuration descriptor.
			libusb_free_config_descriptor(l_pConfig);
			l_pConfig = nullptr;
			if (l_FoundInterface) break;
			
		} // Loop through configuration descriptors.
		
		// Make sure we found the interface.
		if (!l_FoundInterface)
			throw Exception(USBTMC_ERROR_FIND_RESRC_NO_USBTMC_USB488, MTL__LOCATION__);
		
		// Get the handle to the device.
		m_Status = libusb_open(m_pDevice, &m_hDevice);
		if (m_Status != LIBUSB_SUCCESS)
			throw Exception(m_Status, MTL__LOCATION__);

		// Set to detach the kernel driver automatically.
		// Note: this might return LIBUSB_ERROR_NOT_SUPPORTED, but we don't care.
		libusb_set_auto_detach_kernel_driver (m_hDevice, 1);
		
		// Set the current configuration, if needed.
		int l_CurrentConfig;
		m_Status = libusb_get_configuration(m_hDevice, &l_CurrentConfig);
		if (m_Status != LIBUSB_SUCCESS)
			throw Exception(m_Status, MTL__LOCATION__);
		if (l_CurrentConfig != m_ConfigurationNo)
		{
			m_Status =libusb_set_configuration(m_hDevice, m_ConfigurationNo);
			if (m_Status != LIBUSB_SUCCESS)
				throw Exception(m_Status, MTL__LOCATION__);
		}
		
		// Register the device with our access-control mechanism.
		l_USBTMCAccessControl.Register(m_pDevice);
	}
	
	// Handle errors.
	catch (MTL::CException<CUSBTMCInstrument> & rE)
	{
        MTL_Unused(rE)
		MTL_USBTMC_INSTRUMENT_DEBUG_CERR(rE.what());
		
        if (l_pConfig != nullptr)
			libusb_free_config_descriptor(l_pConfig);
		
        if (m_hDevice != nullptr)
		{
			libusb_close(m_hDevice);
			m_hDevice				= nullptr;
		}
		
        if (m_pDevice != nullptr)
		{
			libusb_unref_device(m_pDevice);
			m_pDevice				= nullptr;
			
			m_ConfigurationNo		= 0;
			m_InterfaceNo			= 0;
			m_InterruptEndpoint		= 0;
			m_BulkInEndpoint		= 0;
			m_BulkOutEndpoint		= 0;
			m_BulkInMaxPacketSize	= 0;
			m_bTag					= 0;
			
			Unlock();
		}
		return false;
	}
	
	return true;
	
} // CUSBTMCInstrument::Open

void CUSBTMCInstrument::Close(void)
{
	MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);
	
	// If the device is already closed, we're done.
    if (!CUSBTMCInstrument::IsOpen()) return;
	
	// Unlock the interface.
    CUSBTMCInstrument::Unlock();
	
	// Close the device.
	if (m_hDevice != nullptr)
	{
		libusb_close(m_hDevice);
		m_hDevice = nullptr;
	}

	// Unreference the device pointer.
	if (m_pDevice != nullptr)
	{
		libusb_unref_device(m_pDevice);
		m_pDevice = nullptr;
	}

	// Unregister the device from our access-control mechanism.
	l_USBTMCAccessControl.Unregister(m_pDevice);

	// Clear the other class variables.
	m_ConfigurationNo		= 0;
	m_InterfaceNo			= 0;
	m_InterruptEndpoint		= 0;
	m_BulkInEndpoint		= 0;
	m_BulkOutEndpoint		= 0;
	m_BulkInMaxPacketSize	= 0;
	m_bTag					= 0;

} // CUSBTMCInstrument::Close

bool CUSBTMCInstrument::IsOpen(void)
{
	MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);
	
	return (m_pDevice != nullptr && m_hDevice != nullptr);
	
} // CUSBTMCInstrument::IsOpen

//------------------------------------------//
// Info
std::string CUSBTMCInstrument::StatusDescription (I32 Status)
{
	MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	if (Status <= 0)
		return libusb_error_name(Status);
	else
		return USBTMC_ERROR_EXPLANATION[Status];

} // CUSBTMCInstrument::StatusDescription

bool CUSBTMCInstrument::Timeout(void)
{
	MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	return (m_Status == LIBUSB_ERROR_TIMEOUT);
	
} // CUSBTMCInstrument::Timeout

//------------------------------------------//
// Write
// Low-level write.
bool CUSBTMCInstrument::USBTMCWrite(U8 MessageID, const char * pData, const size_t Size, U8 TransferAttributes)
{
	MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);
	
	bool l_InitialLockState	= m_ExclusiveLock;
	U8 *	l_pBuffer		= nullptr;
	try
	{
		// Make sure this device is open.
		if (!IsOpen())
			throw Exception(USBTMC_ERROR_DEVICE_NOT_OPEN, MTL__LOCATION__);

		// Lock the device.
		if (!LockExclusive(m_Timeout))
			throw Exception(USBTMC_ERROR_INSTRUMENT_LOCKED, MTL__LOCATION__);

		// Allocate the buffer.
		I32 l_Size			= pData ? static_cast<I32>(Size) : 0;
		I32 l_PaddedSize	= (l_Size + USBTMC_BULK_HEADER_SIZE + 3) & ~0x3;
        l_pBuffer = new U8[static_cast<size_t>(l_PaddedSize)];
		if (!l_pBuffer)
			throw Exception(LIBUSB_ERROR_NO_MEM, MTL__LOCATION__);

        // Increment the bTag; ensure that it is in the valid range for ReadSTB.
        if (++m_bTag < 2 || m_bTag > 127)
            m_bTag = 2;
		
		// Build the write buffer: header, data, padding.
        USBTMCBulkOutHeaderWrite(l_pBuffer, MessageID, m_bTag, static_cast<U32>(Size), TransferAttributes, 0);
        if (pData) std::memcpy(l_pBuffer + USBTMC_BULK_HEADER_SIZE, pData, static_cast<size_t>(l_Size));
        std::memset(l_pBuffer + l_Size + USBTMC_BULK_HEADER_SIZE, 0, static_cast<size_t>(l_PaddedSize - l_Size - USBTMC_BULK_HEADER_SIZE));
		
		// Transfer the data.
		I32 l_Transferred;
        m_Status = libusb_bulk_transfer(m_hDevice,								// dev_handle
										m_BulkOutEndpoint,						// endpoint
										l_pBuffer,								// data
										l_PaddedSize,							// length
										&l_Transferred,							// transferred
										static_cast<unsigned int>(m_Timeout));	// timeout
        if (m_Status != LIBUSB_SUCCESS)
		{
            USBTMCClearBulkOut(m_bTag);	// Clear the endpoint
			Clear();					// Clear the device FIFOs
            throw Exception(m_Status, MTL__LOCATION__);
		}
		if (l_Transferred != l_PaddedSize)
			throw Exception(USBTMC_ERROR_WRITE_INVALID_TRANSFER_COUNT, MTL__LOCATION__);
	}
	
	// Handle errors.
	catch (MTL::CException<CUSBTMCInstrument> & rE)
	{
        MTL_Unused(rE)
		MTL_USBTMC_INSTRUMENT_DEBUG_CERR(rE.what());
		
		// Restore the lock to its initial state.
		if (!l_InitialLockState) Unlock();

		return false;
	}
	
	// Restore the lock to its initial state.
	if (!l_InitialLockState) Unlock();
	
	// Deallocate the buffer.
	if (l_pBuffer) delete l_pBuffer;
	
	return true;
	
} // CUSBTMCInstrument::USBTMCWrite

bool CUSBTMCInstrument::Write(const char * Str)
{
	MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	return USBTMCWrite(DEV_DEP_MSG_OUT, Str, std::strlen(Str), EOM);
	
} // CUSBTMCInstrument::Write(const char * Str)

bool CUSBTMCInstrument::Write(const std::string & rStr)
{
	MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);
	
	return USBTMCWrite(DEV_DEP_MSG_OUT, rStr.c_str(), rStr.size(), EOM);
	
} // CUSBTMCInstrument::Write(const std::string & rStr)

bool CUSBTMCInstrument::Write(const CSCPIBuffer & rBuf)
{
	MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);
	
	return USBTMCWrite(DEV_DEP_MSG_OUT, rBuf.data(), rBuf.size(), EOM);
	
} // CUSBTMCInstrument::Write(const CSCPIBuffer & rBuf)

//------------------------------------------//
// Read
bool CUSBTMCInstrument::Read(CSCPIBuffer & rBuf, bool Append)
{
	MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	bool l_InitialLockState	= m_ExclusiveLock;
	try
	{
		// Make sure this device is open.
		if (!IsOpen())
			throw Exception(USBTMC_ERROR_DEVICE_NOT_OPEN, MTL__LOCATION__);
		
		// Lock the device.
		if (!LockExclusive(m_Timeout))
			throw Exception(USBTMC_ERROR_INSTRUMENT_LOCKED, MTL__LOCATION__);

		// Clear the buffer if we're not appending.
		if (!Append) rBuf.clear();
		
		// Write REQUEST_DEV_DEP_MSG_IN, with an enormous buffer size.
		USBTMCWrite(REQUEST_DEV_DEP_MSG_IN, nullptr, INT32_MAX, 0);
		if (m_Status != LIBUSB_SUCCESS)
			throw Exception(m_Status, MTL__LOCATION__);
		
		// Read the header and at least the beginning of the message.
		// Note: since max packet size > header length, we can't read just the header.
		// Note: sometimes, for reasons unknown, libusb_bulk_transfer returns success but l_Transferred = 0
		//		 (does not appear to be a zero-length USB transfer). In this case, repeat the transfer.
		assert(m_BulkInMaxPacketSize >= USBTMC_BULK_HEADER_SIZE);
		I32 l_BufferLength = m_BulkInMaxPacketSize;
		assert(l_BufferLength <= USBTMC_READ_BUFFER_SIZE);
		U8	l_ReadBuffer[USBTMC_READ_BUFFER_SIZE];
		I32	l_Transferred = 0;
		do
		{
			m_Status = libusb_bulk_transfer(m_hDevice,								// dev_handle
											m_BulkInEndpoint,						// endpoint
											l_ReadBuffer,							// data
											l_BufferLength,							// length
											&l_Transferred,							// transferred
											static_cast<unsigned int>(m_Timeout));	// timeout
			if (m_Status < 0)
			{
                USBTMCClearBulkIn(m_bTag);
				throw Exception(m_Status, MTL__LOCATION__);
			}
		} while (l_Transferred == 0);
		if (l_Transferred < USBTMC_BULK_HEADER_SIZE)
			throw Exception(USBTMC_ERROR_READ_INVALID_HEADER, MTL__LOCATION__);
		
		// Parse and check the header.
		I32 l_MessageSize;
		U8	l_TransferAttributes;
        if (!USBTMCBulkInHeaderRead(l_ReadBuffer, DEV_DEP_MSG_IN, m_bTag, l_MessageSize, l_TransferAttributes) ||
			!(l_TransferAttributes & EOM))
			throw Exception(USBTMC_ERROR_READ_INVALID_HEADER, MTL__LOCATION__);
		
		// If necessary, adjust the buffer size to accenpt the entire message.
		size_t	l_BufSize		= rBuf.size();
		size_t	l_BufCapacity	= rBuf.capacity();
		if (l_MessageSize > static_cast<I32>(l_BufCapacity - l_BufSize))
            rBuf.reserve(l_BufSize + static_cast<size_t>(l_MessageSize));
		
		// Copy the part of the message that we have already received.
        size_t l_MessageReceived = static_cast<size_t>(l_Transferred) - USBTMC_BULK_HEADER_SIZE;
		if (l_MessageReceived > 0)
			std::memcpy(rBuf.data() + l_BufSize, l_ReadBuffer + USBTMC_BULK_HEADER_SIZE, l_MessageReceived);
		
		// Read the rest of the message, if applicable.
        size_t l_MessagePending = static_cast<size_t>(l_MessageSize) - l_MessageReceived;
		if (l_MessagePending > 0)
		{
            // Note: small delay before second read. This alleviates a problem - probably host/device specific -
            // where the device does not flush its FIFO.
            std::this_thread::sleep_for(std::chrono::milliseconds(MTL_USBTMC_PAUSE_BETWEEN_READS));
			U8 * l_pData = reinterpret_cast<U8 *>(rBuf.data() + l_BufSize + l_MessageReceived);
			m_Status = libusb_bulk_transfer(m_hDevice,								// dev_handle
											m_BulkInEndpoint,						// endpoint
											l_pData,								// data
											static_cast<I32>(l_MessagePending),		// length
											&l_Transferred,							// transferred
											static_cast<unsigned int>(m_Timeout));	// timeout
			if (m_Status < 0)
			{
                USBTMCClearBulkIn(m_bTag);
				throw Exception(m_Status, MTL__LOCATION__);
			}
			if (l_Transferred != static_cast<I32>(l_MessagePending))
				throw Exception(USBTMC_ERROR_READ_WRONG_MSG_SIZE, MTL__LOCATION__);
		}
		
		// Resize the buffer to the cumulated number of bytes read.
        rBuf.resize(l_BufSize + static_cast<size_t>(l_MessageSize));
	}
	
	// Handle errors.
	catch (MTL::CException<CUSBTMCInstrument> & rE)
	{
        MTL_Unused(rE)
		MTL_USBTMC_INSTRUMENT_DEBUG_CERR(rE.what());
		
		// Restore the lock to its initial state.
		if (!l_InitialLockState) Unlock();
		
		return false;
	}
	
	// Restore the lock to its initial state.
	if (!l_InitialLockState) Unlock();
	
	return true;
	
} // CUSBTMCInstrument::Read

//------------------------------------------//
// Other operations
bool CUSBTMCInstrument::USBTMCClearBulkIn(U8 bTag)
{
	MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);
	
	try
	{
        // Clear the STALL condition, if any.
        I32 l_ReturnValue = libusb_clear_halt(m_hDevice, m_BulkInEndpoint);
        if (l_ReturnValue < 0)
            throw Exception(l_ReturnValue, MTL__LOCATION__);

        // Initiate the Abort BulkIn.
        // Note: use default timeout, since user-supplied timeout may be unsuitable.
        assert(USBTMC_INITIATE_ABORT_BULKIN_RESP_LENGTH		<= USBTMC_READ_BUFFER_SIZE);
		assert(USBTMC_CHECK_ABORT_BULKIN_STATUS_RESP_LENGTH	<= USBTMC_READ_BUFFER_SIZE);
		assert(m_BulkInMaxPacketSize						<= USBTMC_READ_BUFFER_SIZE);
		U8 l_ReadBuffer[USBTMC_READ_BUFFER_SIZE];
        l_ReturnValue = libusb_control_transfer(
                    m_hDevice,									// dev_handle
                    USBTMC_BM_REQUEST_TYPE_IN_CLS_EP,			// bmRequestType
                    INITIATE_ABORT_BULK_IN,						// bRequest
                    static_cast<U16>(bTag),						// wValue
                    m_BulkInEndpoint,							// wIndex
                    l_ReadBuffer,								// data
                    USBTMC_INITIATE_ABORT_BULKIN_RESP_LENGTH,	// wLength
                    IEEE488_DEFAULT_TIMEOUT);                   // timeout
		
		// Check for errors.
		if (l_ReturnValue < 0)
			throw Exception(l_ReturnValue, MTL__LOCATION__);
		if (l_ReturnValue != USBTMC_INITIATE_ABORT_BULKIN_RESP_LENGTH ||
			l_ReadBuffer[0] != USBTMC_STATUS_SUCCESS)
			throw Exception(USBTMC_ERROR_CLEAR_BULKIN_RESPONSE_ERROR, MTL__LOCATION__);
		
		// Flush the device's transfer FIFO.
        // Note: use default timeout, since user-supplied timeout may be unsuitable.
        I32 l_BufferLength = m_BulkInMaxPacketSize;
		I32	l_Transferred;
		do
		{
            l_ReturnValue = libusb_bulk_transfer(
                        m_hDevice,								// dev_handle
                        m_BulkInEndpoint,						// endpoint
                        l_ReadBuffer,							// data
                        l_BufferLength,                         // length
                        &l_Transferred,                         // transferred
                        IEEE488_DEFAULT_TIMEOUT);               // timeout
			if (l_ReturnValue < 0)
				throw Exception(m_Status, MTL__LOCATION__);
		} while (l_Transferred >= l_BufferLength);
		
		// Loop to poll the Initiate BulkIn Abort status.
        // Note: use default timeout, since user-supplied timeout may be unsuitable.
        U8 l_Status;
		do
		{
            l_ReturnValue = libusb_control_transfer(
                        m_hDevice,                                      // dev_handle
                        USBTMC_BM_REQUEST_TYPE_IN_CLS_EP,				// bmRequestType
                        CHECK_ABORT_BULK_IN_STATUS,						// bRequest
                        0x0000,											// wValue
                        m_BulkInEndpoint,								// wIndex
                        l_ReadBuffer,									// data
                        USBTMC_CHECK_ABORT_BULKIN_STATUS_RESP_LENGTH,	// wLength
                        IEEE488_DEFAULT_TIMEOUT);                       // timeout
			
			if (l_ReturnValue < 0)
				throw Exception(l_ReturnValue, MTL__LOCATION__);
			if (l_ReturnValue != USBTMC_CHECK_ABORT_BULKIN_STATUS_RESP_LENGTH ||
				((l_Status = l_ReadBuffer[0]) != USBTMC_STATUS_SUCCESS && l_Status != USBTMC_STATUS_PENDING) ||
				l_ReadBuffer[1] != 0 ||	// BulkIn FIFO should be empty
				l_ReadBuffer[2] != 0 ||	// Always zero
				l_ReadBuffer[3] != 0)	// Always zero
				throw Exception(USBTMC_ERROR_CLEAR_BULKIN_RESPONSE_ERROR, MTL__LOCATION__);
			
			std::this_thread::sleep_for(std::chrono::milliseconds(USBTMC_CHECK_CLEAR_STATUS_INTERVAL));
			
		} while (l_Status != USBTMC_STATUS_SUCCESS);
	}
	
	// Handle errors.
	catch (MTL::CException<CUSBTMCInstrument> & rE)
	{
        MTL_Unused(rE)
		MTL_USBTMC_INSTRUMENT_DEBUG_CERR(rE.what());
		return false;
	}

	return true;
	
} // CUSBTMCInstrument::USBTMCClearBulkIn

bool CUSBTMCInstrument::USBTMCClearBulkOut(U8 bTag)
{
	MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);
	
	try
	{
        // Clear the STALL condition, if any.
        I32 l_ReturnValue = libusb_clear_halt(m_hDevice, m_BulkOutEndpoint);
        if (l_ReturnValue < 0)
            throw Exception(l_ReturnValue, MTL__LOCATION__);

        // Initiate the Abort BulkOut.
        // Note: use default timeout, since user-supplied timeout may be unsuitable.
        assert(USBTMC_INITIATE_ABORT_BULKOUT_RESP_LENGTH		<= USBTMC_READ_BUFFER_SIZE);
		assert(USBTMC_CHECK_ABORT_BULKOUT_STATUS_RESP_LENGTH	<= USBTMC_READ_BUFFER_SIZE);
        U8 l_ReadBuffer[USBTMC_READ_BUFFER_SIZE];
        l_ReturnValue = libusb_control_transfer(
                    m_hDevice,									// dev_handle
                    USBTMC_BM_REQUEST_TYPE_IN_CLS_EP,			// bmRequestType
                    INITIATE_ABORT_BULK_OUT,					// bRequest
                    static_cast<U16>(bTag),						// wValue
                    m_BulkOutEndpoint,							// wIndex
                    l_ReadBuffer,								// data
                    USBTMC_INITIATE_ABORT_BULKOUT_RESP_LENGTH,	// wLength
                    IEEE488_DEFAULT_TIMEOUT);                   // timeout
		
		// Check for errors.
		if (l_ReturnValue < 0)
			throw Exception(l_ReturnValue, MTL__LOCATION__);
		if (l_ReturnValue != USBTMC_INITIATE_ABORT_BULKOUT_RESP_LENGTH ||
			l_ReadBuffer[0] != USBTMC_STATUS_SUCCESS)
			throw Exception(USBTMC_ERROR_CLEAR_BULKOUT_RESPONSE_ERROR, MTL__LOCATION__);
		
		// Loop to poll the Initiate BulkOut Abort status.
        // Note: use default timeout, since user-supplied timeout may be unsuitable.
        U8 l_Status;
		do
		{
            l_ReturnValue = libusb_control_transfer(
                        m_hDevice,										// dev_handle
                        USBTMC_BM_REQUEST_TYPE_IN_CLS_EP,				// bmRequestType
                        CHECK_ABORT_BULK_OUT_STATUS,					// bRequest
                        0x0000,											// wValue
                        m_BulkOutEndpoint,								// wIndex
                        l_ReadBuffer,									// data
                        USBTMC_CHECK_ABORT_BULKOUT_STATUS_RESP_LENGTH,	// wLength
                        IEEE488_DEFAULT_TIMEOUT);                       // timeout
			
			if (l_ReturnValue < 0)
				throw Exception(l_ReturnValue, MTL__LOCATION__);
			if (l_ReturnValue != USBTMC_CHECK_ABORT_BULKOUT_STATUS_RESP_LENGTH ||
				((l_Status = l_ReadBuffer[0]) != USBTMC_STATUS_SUCCESS && l_Status != USBTMC_STATUS_PENDING) ||
				l_ReadBuffer[1] != 0 ||	// Always zero
				l_ReadBuffer[2] != 0 ||	// Always zero
				l_ReadBuffer[3] != 0)	// Always zero
				throw Exception(USBTMC_ERROR_CLEAR_BULKOUT_RESPONSE_ERROR, MTL__LOCATION__);
			
			std::this_thread::sleep_for(std::chrono::milliseconds(USBTMC_CHECK_CLEAR_STATUS_INTERVAL));
			
		} while (l_Status != USBTMC_STATUS_SUCCESS);
		
	}
	
	// Handle errors.
	catch (MTL::CException<CUSBTMCInstrument> & rE)
	{
        MTL_Unused(rE)
		MTL_USBTMC_INSTRUMENT_DEBUG_CERR(rE.what());
		return false;
	}
	
	return true;
	
} // CUSBTMCInstrument::USBTMCClearBulkOut

bool CUSBTMCInstrument::Clear(void)
{
	MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	bool l_InitialLockState	= m_ExclusiveLock;
	try
	{
		// Make sure this device is open.
		if (!IsOpen())
			throw Exception(USBTMC_ERROR_DEVICE_NOT_OPEN, MTL__LOCATION__);
		
		// Lock the device.
		if (!LockExclusive(m_Timeout))
			throw Exception(USBTMC_ERROR_INSTRUMENT_LOCKED, MTL__LOCATION__);

		// Initiate the Clear.
        // Note: use default timeout, since user-supplied timeout may be unsuitable.
		assert(USBTMC_INITIATE_CLEAR_RESP_LENGTH		<= USBTMC_READ_BUFFER_SIZE);
		assert(USBTMC_CHECK_CLEAR_STATUS_RESP_LENGTH	<= USBTMC_READ_BUFFER_SIZE);
		U8 l_ReadBuffer[USBTMC_READ_BUFFER_SIZE];
        I32 l_ReturnValue = libusb_control_transfer(
                    m_hDevice,			// dev_handle
                    USBTMC_BM_REQUEST_TYPE_IN_CLS_IF,		// bmRequestType
                    INITIATE_CLEAR,							// bRequest
                    0x0000,									// wValue
                    USB_CONTROL_ENDPOINT_NUMBER,			// wIndex
                    l_ReadBuffer,							// data
                    USBTMC_INITIATE_CLEAR_RESP_LENGTH,		// wLength
                    IEEE488_DEFAULT_TIMEOUT);               // timeout

		// Check for errors.
		if (l_ReturnValue < 0)
			throw Exception(l_ReturnValue, MTL__LOCATION__);
		if (l_ReturnValue != USBTMC_INITIATE_CLEAR_RESP_LENGTH ||
			l_ReadBuffer[0] != USBTMC_STATUS_SUCCESS)
			throw Exception(USBTMC_ERROR_CLEAR_RESPONSE_ERROR, MTL__LOCATION__);

		// Loop to poll the clear status.
        // Note: use default timeout, since user-supplied timeout may be unsuitable.
        U8 l_Status, l_Clear;
		do
		{
            l_ReturnValue = libusb_control_transfer(
                        m_hDevice,                              // dev_handle
                        USBTMC_BM_REQUEST_TYPE_IN_CLS_IF,		// bmRequestType
                        CHECK_CLEAR_STATUS,						// bRequest
                        0x0000,									// wValue
                        USB_CONTROL_ENDPOINT_NUMBER,			// wIndex
                        l_ReadBuffer,							// data
                        USBTMC_CHECK_CLEAR_STATUS_RESP_LENGTH,	// wLength
                        IEEE488_DEFAULT_TIMEOUT);               // timeout

			if (l_ReturnValue < 0)
				throw Exception(l_ReturnValue, MTL__LOCATION__);
			if (l_ReturnValue != USBTMC_CHECK_CLEAR_STATUS_RESP_LENGTH ||
				((l_Status = l_ReadBuffer[0]) != USBTMC_STATUS_SUCCESS && l_Status != USBTMC_STATUS_PENDING) ||
				((l_Clear = l_ReadBuffer[1]) != 0 && l_Clear != BULKIN_FIFO_BYTES) )
				throw Exception(USBTMC_ERROR_CLEAR_RESPONSE_ERROR, MTL__LOCATION__);

			std::this_thread::sleep_for(std::chrono::milliseconds(USBTMC_CHECK_CLEAR_STATUS_INTERVAL));
			
		} while (l_Status != USBTMC_STATUS_SUCCESS);
		
		// Clear the BulkOut Halt condition.
		l_ReturnValue = libusb_clear_halt(m_hDevice, m_BulkOutEndpoint);
		if (l_ReturnValue < 0)
			throw Exception(l_ReturnValue, MTL__LOCATION__);

		// If necessary, clear the BulkIn endpoint.
		if (l_Clear == BULKIN_FIFO_BYTES)
            USBTMCClearBulkIn(m_bTag);
	}
	
	// Handle errors.
	catch (MTL::CException<CUSBTMCInstrument> & rE)
	{
        MTL_Unused(rE)
		MTL_USBTMC_INSTRUMENT_DEBUG_CERR(rE.what());
		
		// Restore the lock to its initial state.
		if (!l_InitialLockState) Unlock();

		return false;
	}
	
	// Restore the lock to its initial state.
	if (!l_InitialLockState) Unlock();

	return true;
	
} // CUSBTMCInstrument::Clear

bool CUSBTMCInstrument::ReadSTB(U16 & rSTB)
{
	MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	bool l_InitialLockState	= m_ExclusiveLock;
	try
	{
		// Make sure this device is open.
		if (!IsOpen())
			throw Exception(USBTMC_ERROR_DEVICE_NOT_OPEN, MTL__LOCATION__);
		
		// Lock the device.
		if (!LockExclusive(m_Timeout))
			throw Exception(USBTMC_ERROR_INSTRUMENT_LOCKED, MTL__LOCATION__);

		// Loop to read the Status Byte.
		// Since we don't handle Service Requests at the moment (TODO), just flush them.
		// Note that Service Requests have the same packet length, but the bTag is 0x01 instead of the one we specify.
		assert(USBTMC_READSTB_CTRL_RESP_LENGTH	<= USBTMC_READ_BUFFER_SIZE);
		assert(USBTMC_READSTB_INTR_RESP_LENGTH	<= USBTMC_READ_BUFFER_SIZE);
        bool l_GotSTB           = false;
        bool l_WriteCommand     = true;
        bool l_Success          = false;
        bool l_InterruptInBusy  = false;
        t_InterruptInData l_InterruptInPacket;
		do
		{
            // Write the READ_STATUS_BYTE command only if required.
            if (l_WriteCommand)
            {
                // Increment the bTag; ensure that it is in the valid range for ReadSTB.
                if (++m_bTag < 2 || m_bTag > 127)
                    m_bTag = 2;

                // Send the READ_STATUS_BYTE command.
                U8 l_ReadBuffer[USBTMC_READ_BUFFER_SIZE];
                I32 l_ReturnValue = libusb_control_transfer(
                            m_hDevice,                              // dev_handle
                            USBTMC_BM_REQUEST_TYPE_IN_CLS_IF,		// bmRequestType
                            READ_STATUS_BYTE,						// bRequest
                            m_bTag,									// wValue
                            USB_CONTROL_ENDPOINT_NUMBER,			// wIndex
                            l_ReadBuffer,							// data
                            USBTMC_READSTB_CTRL_RESP_LENGTH,		// wLength
                            static_cast<unsigned int>(m_Timeout));	// timeout

                // Check for errors.
                if (l_ReturnValue < 0)
                    throw Exception(l_ReturnValue, MTL__LOCATION__);
                l_Success			= USBTMCReadSTBResponse(l_ReadBuffer, USBTMC_STATUS_SUCCESS, m_bTag, 0);
                l_InterruptInBusy	= USBTMCReadSTBResponse(l_ReadBuffer, USBTMC_STATUS_INTERRUPT_IN_BUSY, m_bTag, 0);
                if (l_ReturnValue != USBTMC_READSTB_CTRL_RESP_LENGTH ||
                        !(l_Success || l_InterruptInBusy))
                    throw Exception(USBTMC_ERROR_READSTB_CONTROL_RESPONSE_ERROR, MTL__LOCATION__);

                // Do not write another READ_STATUS_BYTE command unless asked to.
                l_WriteCommand = false;
            }

            // Read the next packet from the Interrupt-In endpoint.
			I32 l_Transferred;
            I32 l_ReturnValue = libusb_interrupt_transfer(
                        m_hDevice,                                    // dev_handle
                        m_InterruptEndpoint,                          // endpoint
                        reinterpret_cast<U8 *>(&l_InterruptInPacket), // data
                        USBTMC_READSTB_INTR_RESP_LENGTH,              // length
                        &l_Transferred,                               // transferred
                        static_cast<unsigned int>(m_Timeout));        // timeout
			
            // If we had an "Interrupt-In Busy" warning and the Interrupt-In FIFO is now empty,
            // set up to send another READ_STATUS_BYTE command.
            if (l_InterruptInBusy && l_ReturnValue == LIBUSB_ERROR_TIMEOUT)
            {
                l_WriteCommand = true;
                continue;
            }

            // Check for other errors.
            if (l_ReturnValue < 0)
				throw Exception(l_ReturnValue, MTL__LOCATION__);
			if (l_Transferred != USBTMC_READSTB_INTR_RESP_LENGTH)
				throw Exception(USBTMC_ERROR_READSTB_INTERRUPT_RESPONSE_ERROR, MTL__LOCATION__);
			
            // If enabled, Service Requests are enqueued; otherwise they are discarded.
            if (l_InterruptInPacket.bNotify1 == 0x81)
            {
                if (m_SRQEnable) m_SRQQueue.push(l_InterruptInPacket);
				continue;
            }

			// Validate the Read STB response.
            if (l_InterruptInPacket.bNotify1 != (0x80 | m_bTag))
				throw Exception(USBTMC_ERROR_READSTB_INTERRUPT_RESPONSE_ERROR, MTL__LOCATION__);

            // Return the status byte.
            rSTB = l_InterruptInPacket.bNotify2;
            l_GotSTB = true;
		} while (!l_GotSTB);
		
	}
	
	// Handle errors.
	catch (MTL::CException<CUSBTMCInstrument> & rE)
	{
        MTL_Unused(rE)
		MTL_USBTMC_INSTRUMENT_DEBUG_CERR(rE.what());
		
		// Restore the lock to its initial state.
		if (!l_InitialLockState) Unlock();

		return false;
	}
	
	// Restore the lock to its initial state.
	if (!l_InitialLockState) Unlock();

	return true;
	
} // CUSBTMCInstrument::ReadSTB

bool CUSBTMCInstrument::AssertTrigger(void)
{
	MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);
	
	bool l_InitialLockState	= m_ExclusiveLock;
	try
	{
		// Make sure this device is open.
		if (!IsOpen())
			throw Exception(USBTMC_ERROR_DEVICE_NOT_OPEN, MTL__LOCATION__);
		
		// Lock the device.
		if (!LockExclusive(m_Timeout))
			throw Exception(USBTMC_ERROR_INSTRUMENT_LOCKED, MTL__LOCATION__);

        // Increment the bTag; ensure that it is in the valid range for ReadSTB.
        if (++m_bTag < 2 || m_bTag > 127)
            m_bTag = 2;

		// Build the write buffer: everything except MsgID, bTag, and ~bTag is zero.
		U8 l_Buffer[USBTMC_BULK_TRIGGER_MSG_SIZE];
        USBTMCBulkOutHeaderWrite(l_Buffer, TRIGGER, m_bTag, 0, 0, 0);
		
		// Transfer the data.
		I32 l_Transferred;
		m_Status = libusb_bulk_transfer(m_hDevice,			// dev_handle
										m_BulkOutEndpoint,	// endpoint
										l_Buffer,								// data
										USBTMC_BULK_TRIGGER_MSG_SIZE,			// length
										&l_Transferred,							// transferred
										static_cast<unsigned int>(m_Timeout));	// timeout
		if (m_Status != LIBUSB_SUCCESS)
		{
            USBTMCClearBulkOut(m_bTag);	// Clear the endpoint
			Clear();							// Clear the device FIFOs
			throw Exception(m_Status, MTL__LOCATION__);
		}
		if (l_Transferred != USBTMC_BULK_TRIGGER_MSG_SIZE)
			throw Exception(USBTMC_ERROR_WRITE_INVALID_TRANSFER_COUNT, MTL__LOCATION__);
	}
	
	// Handle errors.
	catch (MTL::CException<CUSBTMCInstrument> & rE)
	{
        MTL_Unused(rE)
		MTL_USBTMC_INSTRUMENT_DEBUG_CERR(rE.what());
		
		// Restore the lock to its initial state.
		if (!l_InitialLockState) Unlock();

		return false;
	}

	// Restore the lock to its initial state.
	if (!l_InitialLockState) Unlock();

	return true;
	
} // CUSBTMCInstrument::AssertTrigger

//------------------------------------------//
// Lock / Unlock
bool CUSBTMCInstrument::LockExclusive(U32 Timeout)
{
	MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

	// If it is already locked, we're done.
	if (m_ExclusiveLock) return true;
	
	// Try to claim the interface.
	if (!l_USBTMCAccessControl.Claim(m_pDevice, Timeout))
	{
		m_Status = USBTMC_ERROR_INSTRUMENT_LOCKED;
		return false;
	}
	
	// Tell libusb to claim the interface.
	I32 l_ReturnCode = libusb_claim_interface(m_hDevice, m_InterfaceNo);
	if (l_ReturnCode != LIBUSB_SUCCESS)
	{
		m_Status = l_ReturnCode;
		l_USBTMCAccessControl.Release(m_pDevice);
		return false;
	}
	
	// Lock succeeded.
	m_ExclusiveLock = true;
	return true;
	
} // CUSBTMCInstrument::LockExclusive

bool CUSBTMCInstrument::Unlock(void)
{
	MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
	CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

		// If it is already unlocked, we're done.
		if (!m_ExclusiveLock) return true;
		
		// Tell libusb to release the interface.
		I32 l_ReturnCode = libusb_release_interface(m_hDevice, m_InterfaceNo);
		if (l_ReturnCode != LIBUSB_SUCCESS)
		{
			m_Status = l_ReturnCode;
			return false;
		}

		// Release the claim to the interface.
		l_USBTMCAccessControl.Release(m_pDevice);

	m_ExclusiveLock = false;
	return true;

} // CUSBTMCInstrument::Unlock

bool CUSBTMCInstrument::LockedExclusive(void)
{
    return m_ExclusiveLock;

} // CUSBTMCInstrument::Locked


//------------------------------------------//
// Exception
MTL::CException<CUSBTMCInstrument> CUSBTMCInstrument::Exception(I32 Status, std::string Location)
{
	m_Status = Status;
	return MTL::CException<CUSBTMCInstrument>(StatusDescription(Status), Location);
}

//------------------------------------------//
// Service requests
bool CUSBTMCInstrument::EnableEvent (void)
{
    MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
    CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

    m_SRQEnable = true;
    return true;

} // CUSBTMCInstrument::EnableEvent

bool CUSBTMCInstrument::DisableEvent (void)
{
    MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
    CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

    m_SRQEnable = false;
    return true;

} // CUSBTMCInstrument::DisableEvent

bool CUSBTMCInstrument::WaitOnEvent (U32 Timeout)
{
    MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
    CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

    bool l_InitialLockState	= m_ExclusiveLock;
    try
    {
        // Make sure this device is open.
        if (!IsOpen())
            throw Exception(USBTMC_ERROR_DEVICE_NOT_OPEN, MTL__LOCATION__);

        // Make sure Service Requests are enabled.
        if (!m_SRQEnable)
            throw Exception(USBTMC_ERROR_SRQ_NOT_ENABLED, MTL__LOCATION__);

        // If we have one, take a queued-up Service Request.
        if (m_SRQQueue.size() > 0)
        {
            m_SRQQueue.pop();
        }

        // Else read the next packet from the Interrupt-In endpoint.
        else
        {
            // Lock the device.
            if (!LockExclusive(m_Timeout))
                throw Exception(USBTMC_ERROR_INSTRUMENT_LOCKED, MTL__LOCATION__);

            // Read the Interrupt-In packet.
            t_InterruptInData l_InterruptInPacket;
            I32 l_Transferred;
            I32 l_ReturnValue = libusb_interrupt_transfer(
                        m_hDevice,                                    // dev_handle
                        m_InterruptEndpoint,                          // endpoint
                        reinterpret_cast<U8 *>(&l_InterruptInPacket), // data
                        USBTMC_READSTB_INTR_RESP_LENGTH,              // length
                        &l_Transferred,                               // transferred
                        static_cast<unsigned int>(Timeout));          // timeout

            // Check for errors.
            if (l_ReturnValue < 0)
                throw Exception(l_ReturnValue, MTL__LOCATION__);
            if (l_Transferred != USBTMC_READSTB_INTR_RESP_LENGTH ||
                    l_InterruptInPacket.bNotify1 != 0x81)
                throw Exception(USBTMC_ERROR_READSTB_INTERRUPT_RESPONSE_ERROR, MTL__LOCATION__);
        }
    }

    // Handle errors.
    catch (MTL::CException<CUSBTMCInstrument> & rE)
    {
        MTL_Unused(rE)
        MTL_USBTMC_INSTRUMENT_DEBUG_CERR(rE.what());

        // Restore the lock to its initial state.
        if (!l_InitialLockState) Unlock();

        return false;
    }

    // Restore the lock to its initial state.
    if (!l_InitialLockState) Unlock();

    return true;

} // CUSBTMCInstrument::WaitOnEvent

bool CUSBTMCInstrument::DiscardEvents (void)
{
    MTL_USBTMC_INSTRUMENT_DEBUG_COUT(MTL__FUNCTION_NAME__ << std::endl);
    CLockGuard<CRecursiveMutex> l_LockGuard(m_Lock);

    bool l_InitialLockState	= m_ExclusiveLock;
    try
    {
        // Make sure this device is open.
        if (!IsOpen())
            throw Exception(USBTMC_ERROR_DEVICE_NOT_OPEN, MTL__LOCATION__);

        // Make sure Service Requests are enabled.
        if (!m_SRQEnable)
            throw Exception(USBTMC_ERROR_SRQ_NOT_ENABLED, MTL__LOCATION__);

        // Lock the device.
        if (!LockExclusive(m_Timeout))
            throw Exception(USBTMC_ERROR_INSTRUMENT_LOCKED, MTL__LOCATION__);

        // Flush the Service-Request queue.
        while (m_SRQQueue.size() > 0)
            m_SRQQueue.pop();

        // Flush the instrument's Interrupt-In FIFO.
        while (true)
        {
            // Read an Interrupt-In packet.
            t_InterruptInData l_InterruptInPacket;
            I32 l_Transferred;
            I32 l_ReturnValue = libusb_interrupt_transfer(
                        m_hDevice,                                    // dev_handle
                        m_InterruptEndpoint,                          // endpoint
                        reinterpret_cast<U8 *>(&l_InterruptInPacket), // data
                        USBTMC_READSTB_INTR_RESP_LENGTH,              // length
                        &l_Transferred,                               // transferred
                        static_cast<unsigned int>(m_Timeout));        // timeout

            // We're done if we get a timeout error.
            if (l_ReturnValue == LIBUSB_ERROR_TIMEOUT)
                break;

            // Check for other errors.
            if (l_ReturnValue < 0)
                throw Exception(l_ReturnValue, MTL__LOCATION__);
            if (l_Transferred != USBTMC_READSTB_INTR_RESP_LENGTH ||
                    l_InterruptInPacket.bNotify1 != 0x81)
                throw Exception(USBTMC_ERROR_READSTB_INTERRUPT_RESPONSE_ERROR, MTL__LOCATION__);
        }
    }

    // Handle errors.
    catch (MTL::CException<CUSBTMCInstrument> & rE)
    {
        MTL_Unused(rE)
        MTL_USBTMC_INSTRUMENT_DEBUG_CERR(rE.what());

        // Restore the lock to its initial state.
        if (!l_InitialLockState) Unlock();

        return false;
    }

    // Restore the lock to its initial state.
    if (!l_InitialLockState) Unlock();

    return true;

} // CUSBTMCInstrument::DiscardEvents
