using System; using System.Threading; using System.Runtime.InteropServices; using DTS.Common.DASResource; using DTS.Common.USBFramework; using DTS.Common.Utilities.Logging; using DTS.Common.Interface.Connection; using DTS.Common.Classes.Connection; using System.Threading.Tasks; namespace DTS.Common.WINUSBConnection { public class WINUSBConnection : IConnection { /// /// returns true if the unit is currently soft disconnected /// public bool IsSoftDisconnected { get; private set; } = false; /// /// connect the unit /// :note does nothing for WINUSB as we have no disconnect option yet: /// public void SoftConnect() { } /// /// disconnect the unit /// :note does nothing for WINUSB as we have no disconnect option yet: /// public void SoftDisconnect() { } void IConnection.KeepAliveErrorReceived() { } public string GetConnectionData() { return ""; } private readonly DeviceManagement _wusbDeviceManagement; private WinUsbDevice _sliceDev; public event EventHandler OnDisconnected; public double GetCurrentUploadRate() { return 0D; } public double GetCurrentDownloadRate() { return 0D; } public bool Connected { get; private set; } public string ConnectString { get; private set; } public System.Net.Sockets.SocketFlags Flags { get; set; } private class AUSBRecFix { public readonly AsyncCallback Callback; public WinUSBRecAsyncResult USBResult; public AUSBRecFix(AsyncCallback callback, WinUSBRecAsyncResult usbResult) { Callback = callback; USBResult = usbResult; } } public class WinUSBRecAsyncResult : IAsyncResult { public object AsyncState { get; set; } public WaitHandle AsyncWaitHandle { get; set; } public bool CompletedSynchronously { get; set; } public bool IsCompleted { get; set; } public byte[] Buffer { get; set; } public int Offset { get; set; } public int Size { get; set; } public WinUSBRecAsyncResult(object state) { AsyncState = state; AsyncWaitHandle = new ManualResetEvent(false); CompletedSynchronously = false; IsCompleted = false; Buffer = null; Offset = 0; Size = 0; } } public WINUSBConnection() { Disposed = false; ConnectString = string.Empty; Connected = false; _wusbDeviceManagement = new DeviceManagement(); _sliceDev = null; } #region Exit handling ~WINUSBConnection() { Dispose(false); try { APILogger.Log("failed to dispose WINUSBConnection - finalizer is disposing now."); } catch { } } public void Dispose() { Dispose(true); // Use SupressFinalize in case a subclass // of this type implements a finalizer. GC.SuppressFinalize(this); } protected volatile bool Disposed; protected int Disposing; private readonly Mutex _usbConnectionMutex = new Mutex(false, "USBConnection"); protected virtual void Dispose(bool disposing) { if (Interlocked.Exchange(ref Disposing, 1) != 0) { return; } // make sure we're not already disposed if (Disposed) return; if (disposing) { try { if (_wusbDeviceManagement != null) { _wusbDeviceManagement.Dispose(); } DisposeSliceDev(); } catch { // we have to suppress all exceptions here } } Connected = false; Disposed = true; } #endregion public void Create(string connectString) { //Create(connectString); this is recursive! } public void Create(string connectString, string hostIPAddress) { ConnectString = connectString; } #region Disconnect public IAsyncResult BeginDisconnect(bool reuseSocket, AsyncCallback cb, Object state) { if (_sliceDev == null) { throw new NotConnectedException("WINUSBConnection is null"); } if (cb == null) { throw new ArgumentException("WINUSBConnection.BeginDisconnect: callback is null"); } Connected = false; var rar = new WinUSBRecAsyncResult(state); if (!ThreadPool.QueueUserWorkItem(NetCallbackFix, new AUSBRecFix(cb, rar))) { throw new Exception(DASResource.Strings.WINUSBConnection_BeginDisconnect_Err1); } return rar; } public void EndDisconnect(IAsyncResult asyncResult) { if (_sliceDev == null) { throw new NotConnectedException("WINUSBConnection is null"); } var rar = asyncResult as WinUSBRecAsyncResult; if (rar == null) { throw new ArgumentException("WINUSBConnection.EndDisconnect: argument is not a WinUSBRecAsyncResult"); } DisposeSliceDev(); ((ManualResetEvent)rar.AsyncWaitHandle).Set(); } private void DisposeSliceDev() { _usbConnectionMutex.WaitOne(1000); try { if (null != _sliceDev) { _sliceDev.Dispose(); _sliceDev = null; } } catch (Exception ex) { APILogger.Log("could not release USB handle ", ex); } finally { _usbConnectionMutex.ReleaseMutex(); } } #endregion #region Accept public IAsyncResult BeginAccept(AsyncCallback callback, Object state) { throw new NotSupportedException(); } public IConnection EndAccept(IAsyncResult asyncResult) { throw new NotSupportedException(); } #endregion #region Listen public void Listen(int backlog) { throw new NotSupportedException(); } #endregion #region Bind public void Bind(int port) { throw new NotSupportedException(); } #endregion #region Connect public IAsyncResult BeginConnect(AsyncCallback cb, object state) { if (_sliceDev != null || Connected) { throw new NotConnectedException("WINUSBConnection.BeginConnect: already connected"); } if (cb == null) { throw new ArgumentException("WINUSBConnection.BeginConnect: callback is null"); } var rar = new WinUSBRecAsyncResult(state); var ausbrf = new AUSBRecFix(cb, rar); (ausbrf?.USBResult.AsyncWaitHandle as ManualResetEvent).Reset(); if (!ThreadPool.QueueUserWorkItem(NetCallbackFix, ausbrf)) { throw new Exception(DASResource.Strings.WINUSBConnection_BeginConnect_Err1); } rar.AsyncWaitHandle.WaitOne(); return rar; } private void NetCallbackFix(object obj) { var ausbrf = obj as AUSBRecFix; try { ausbrf?.Callback(ausbrf.USBResult); } catch (Exception ex) { APILogger.Log("MessageBox", DASResource.Strings.Communication_WinUSBConnectionCallbackFailed, ex); } finally { (ausbrf?.USBResult.AsyncWaitHandle as ManualResetEvent).Set(); } } public void EndConnect(IAsyncResult ar) { _usbConnectionMutex.WaitOne(); var rar = ar as WinUSBRecAsyncResult; try { if (_sliceDev != null || Connected) { throw new NotConnectedException("WINUSBConnection.EndConnect: already connected"); } if (rar == null) { throw new ArgumentException("WINUSBConnection.EndConnect: argument is not a WinUSBRecAsyncResult"); } if (string.IsNullOrEmpty(ConnectString)) { throw new ArgumentException("WINUSBConnection.EndConnect: ConnectString is invalid"); } _sliceDev = new WinUsbDevice(); } catch (Exception ex) { APILogger.Log("exception in connecting USB", ex); throw; } finally { _usbConnectionMutex.ReleaseMutex(); } var success = _sliceDev.GetDeviceHandle(ConnectString); if (!success) { Connected = false; DisposeSliceDev(); ((ManualResetEvent)rar.AsyncWaitHandle).Set(); throw new NotConnectedException("WINUSBConnection.EndConnect: connect failed"); } if (_sliceDev.InitializeDevice()) { Connected = true; } else { var error = DeviceManagementDeclarations.GetLastError(); try { var w32 = new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); APILogger.Log("WINUSBConnection.EndConnect: connect failed: ", w32.Message); } catch { } throw new NotConnectedException("WINUSBConnection.EndConnect: connect failed (" + error + ")"); } ((ManualResetEvent)rar.AsyncWaitHandle).Set(); } #endregion #region Send public IAsyncResult BeginSend(byte[] buffer, int offset, int size, AsyncCallback cb, object state) { if (!Connected || _sliceDev == null) { throw new NotConnectedException("WINUSBConnection is not connected"); } if (buffer == null || buffer.Length == 0 || offset < 0 || size < 0 || offset + size > buffer.Length || cb == null) { throw new ArgumentException("WINUSBConnection.EndConnect: invalid parameters"); } var rar = new WinUSBRecAsyncResult(state) { Buffer = buffer, Offset = offset, Size = size }; if (!ThreadPool.QueueUserWorkItem(NetCallbackFix, new AUSBRecFix(cb, rar))) { throw new Exception(DASResource.Strings.WINUSBConnection_BeginSend_Err1); } return rar; } public int EndSend(IAsyncResult ar) { if (!Connected || _sliceDev == null) { throw new NotConnectedException("WINUSBConnection is not connected"); } var rar = ar as WinUSBRecAsyncResult; if (rar == null) { throw new ArgumentException("WINUSBConnection.EndSend: argument is not a WinUSBRecAsyncResult"); } var bytes = new byte[rar.Size]; Buffer.BlockCopy(rar.Buffer, 0, bytes, 0, rar.Size); if (_sliceDev.MyDevInfo.UseHybridBulkIntMode) { if (!_sliceDev.SendViaInterruptTransfer(ref bytes, (uint)rar.Size)) { Connected = false; ((ManualResetEvent)rar.AsyncWaitHandle).Set(); throw new Exception(DASResource.Strings.WINUSBConnection_EndSend_Err1); } } else { if (!_sliceDev.SendViaBulkTransfer(ref bytes, (uint)rar.Size)) { Connected = false; ((ManualResetEvent)rar.AsyncWaitHandle).Set(); throw new Exception(DASResource.Strings.WINUSBConnection_EndSend_Err1); } } ((ManualResetEvent)rar.AsyncWaitHandle).Set(); return rar.Size; } public Task SendAsync(byte[] sendBuffer, int bufferStartOffset, int bufferSizeToSend) { return Task.Factory.FromAsync( (callback, state) => BeginSend(sendBuffer, bufferStartOffset, bufferSizeToSend, callback, state), EndSend, state: null ); } #endregion #region Receive public IAsyncResult BeginReceive(byte[] buffer, int offset, int size, AsyncCallback cb, object state) { if (!Connected || _sliceDev == null) { throw new NotConnectedException("WINUSBConnection is not connected"); } if (buffer == null || buffer.Length == 0 || offset < 0 || size < 0 || offset + size > buffer.Length || cb == null) { throw new ArgumentException("WINUSBConnection.EndConnect: invalid parameters"); } var rar = new WinUSBRecAsyncResult(state) { Buffer = buffer, Offset = offset, Size = size }; if (!ThreadPool.QueueUserWorkItem(NetCallbackFix, new AUSBRecFix(cb, rar))) { throw new Exception(DASResource.Strings.WINUSBConnection_BeginReceive_Err1); } return rar; } public int EndReceive(IAsyncResult ar) { if (!Connected || _sliceDev == null) { throw new NotConnectedException("WINUSBConnection is not connected"); } var rar = ar as WinUSBRecAsyncResult; if (rar == null) { throw new ArgumentException("WINUSBConnection.EndReceive: argument is not a WinUSBRecAsyncResult"); } try { uint bytesRead = 0; var bytes = new byte[rar.Size]; var success = true; while (success && 0 == bytesRead) { _sliceDev.ReadViaBulkTransfer(Convert.ToByte(_sliceDev.MyDevInfo.BulkInPipe), (uint)rar.Size, ref bytes, ref bytesRead, ref success); if (0 == bytesRead) { Thread.Sleep(1); } } if (success) { Buffer.BlockCopy(bytes, 0, rar.Buffer, 0, (int)bytesRead); rar.Size = (int)bytesRead; ((ManualResetEvent)rar.AsyncWaitHandle).Set(); return rar.Size; } // the connection is now closed Connected = false; ((ManualResetEvent)rar.AsyncWaitHandle).Set(); return 0; } catch (Exception ex) { Connected = false; if (!(ex is NullReferenceException)) { APILogger.LogString("WINUSBConnection.EndReceive: Exception caught"); APILogger.LogException(ex); } return 0; } } #endregion } }