/* WinUSBNet library * (C) 2010 Thomas Bleeker (www.madwizard.org) * * Licensed under the MIT license, see license.txt or: * http://www.opensource.org/licenses/mit-license.php */ using System; using System.Collections.Generic; using System.Text; namespace MadWizard.WinUSBNet { /// /// UsbPipe represents a single pipe on a WinUSB device. A pipe is connected /// to a certain endpoint on the device and has a fixed direction (IN or OUT) /// public class USBPipe { private API.WINUSB_PIPE_INFORMATION _pipeInfo; private USBInterface _interface = null; private USBDevice _device; private USBPipePolicy _policy; private byte[] LastWritten = null; /// /// Endpoint address including the direction in the most significant bit /// public byte Address { get { return _pipeInfo.PipeId; } } /// /// The USBDevice this pipe is associated with /// public USBDevice Device { get { return _device; } } /// /// Maximum packet size for transfers on this endpoint /// public int MaximumPacketSize { get { return _pipeInfo.MaximumPacketSize; } } /// /// The interface associated with this pipe /// public USBInterface Interface { get { return _interface; } } /// /// The pipe policy settings for this pipe /// public USBPipePolicy Policy { get { return _policy; } } /// /// True if the pipe has direction OUT (host to device), false otherwise. /// public bool IsOut { get { return (_pipeInfo.PipeId & 0x80) == 0; } } /// /// True if the pipe has direction IN (device to host), false otherwise. /// public bool IsIn { get { return (_pipeInfo.PipeId & 0x80) != 0; } } /// /// Reads data from the pipe into a buffer. /// /// The buffer to read data into. The maximum number of bytes that will be read is specified by the length of the buffer. /// The number of bytes read from the pipe. public int Read(byte[] buffer) { return Read(buffer, 0, buffer.Length); } /// /// Reads data from the pipe into a buffer. /// /// The buffer to read data into. /// The byte offset in from which to begin writing data read from the pipe. /// The maximum number of bytes to read, starting at offset /// The number of bytes read from the pipe. public int Read(byte[] buffer, int offset, int length) { CheckReadParams(buffer, offset, length); try { uint bytesRead; _device.InternalDevice.ReadPipe(Interface.InterfaceIndex, _pipeInfo.PipeId, buffer, offset, length, out bytesRead); return (int)bytesRead; } catch (API.APIException e) { // throw new USBException("Failed to read from pipe.", e); LogAndThrowException(new USBException("Failed to read from pipe.", e)); return 0; } } private void CheckReadParams(byte[] buffer, int offset, int length) { if (!IsIn) // throw new NotSupportedException("Cannot read from a pipe with OUT direction."); LogAndThrowException(new NotSupportedException("Cannot read from a pipe with OUT direction.")); int bufferLength = buffer.Length; if (offset < 0 || offset >= bufferLength) // throw new ArgumentOutOfRangeException("Offset of data to read is outside the buffer boundaries."); LogAndThrowException(new ArgumentOutOfRangeException("Offset of data to read is outside the buffer boundaries.")); if (length < 0 || (offset + length) > bufferLength) // throw new ArgumentOutOfRangeException("Length of data to read is outside the buffer boundaries."); LogAndThrowException(new ArgumentOutOfRangeException("Length of data to read is outside the buffer boundaries.")); } private void CheckWriteParams(byte[] buffer, int offset, int length) { if (!IsOut) // throw new NotSupportedException("Cannot write to a pipe with IN direction."); LogAndThrowException(new NotSupportedException("Cannot write to a pipe with IN direction.")); int bufferLength = buffer.Length; if (offset < 0 || offset >= bufferLength) // throw new ArgumentOutOfRangeException("Offset of data to write is outside the buffer boundaries."); LogAndThrowException(new ArgumentOutOfRangeException("Offset of data to write is outside the buffer boundaries.")); if (length < 0 || (offset + length) > bufferLength) // throw new ArgumentOutOfRangeException("Length of data to write is outside the buffer boundaries."); LogAndThrowException(new ArgumentOutOfRangeException("Length of data to write is outside the buffer boundaries.")); } /// Initiates an asynchronous read operation on the pipe. /// Buffer that will receive the data read from the pipe. /// Byte offset within the buffer at which to begin writing the data received. /// Length of the data to transfer. /// An optional asynchronous callback, to be called when the operation is complete. Can be null if no callback is required. /// A user-provided object that distinguishes this particular asynchronous operation. Can be null if not required. /// An object repesenting the asynchronous operation, which could still be pending. /// This method always completes immediately even if the operation is still pending. The object returned represents the operation /// and must be passed to to retrieve the result of the operation. For every call to this method a matching call to /// must be made. When specifies a callback function, this function will be called when the operation is completed. The optional /// parameter can be used to pass user-defined information to this callback or the . The /// also provides an event handle () that will be triggered when the operation is complete as well. /// public IAsyncResult BeginRead(byte[] buffer, int offset, int length, AsyncCallback userCallback, object stateObject) { CheckReadParams(buffer, offset, length); USBAsyncResult result = new USBAsyncResult(userCallback, stateObject); try { _device.InternalDevice.ReadPipeOverlapped(Interface.InterfaceIndex, _pipeInfo.PipeId, buffer, offset, length, result); } catch (API.APIException e) { if (result != null) result.Dispose(); // throw new USBException("Failed to read from pipe.", e); LogAndThrowException(new USBException("Failed to read from pipe.", e)); } catch (Exception e) { if (result != null) result.Dispose(); // throw; LogException(e); throw; } return result; } /// /// Waits for a pending asynchronous read operation to complete. /// /// The object representing the asynchonous operation, /// as returned by . /// The number of bytes transfered during the operation. /// Every call to must have a matching call to to dispose /// of any resources used and to retrieve the result of the operation. When the operation was successful the method returns the number /// of bytes that were transfered. If an error occurred during the operation this method will throw the exceptions that would /// otherwise have ocurred during the operation. If the operation is not yet finished EndWrite will wait for the /// operation to finish before returning. public int EndRead(IAsyncResult asyncResult) { if (asyncResult == null) // throw new NullReferenceException("asyncResult cannot be null"); LogAndThrowException(new NullReferenceException("asyncResult cannot be null")); if (!(asyncResult is USBAsyncResult)) // throw new ArgumentException("AsyncResult object was not created by calling BeginRead on this class."); LogAndThrowException(new ArgumentException("AsyncResult object was not created by calling BeginRead on this class.")); // todo: check duplicate end reads? USBAsyncResult result = (USBAsyncResult)asyncResult; try { if (!result.IsCompleted) result.AsyncWaitHandle.WaitOne(); if (result.Error != null) // throw new USBException("Asynchronous read from pipe has failed.", result.Error); LogAndThrowException(new USBException("Asynchronous read from pipe has failed.", result.Error)); return result.BytesTransfered; } finally { result.Dispose(); } } /// /// Writes data from a buffer to the pipe. /// /// The buffer to write data from. The complete buffer will be written to the device. public void Write(byte[] buffer) { Write(buffer, 0, buffer.Length); } /// /// Writes data from a buffer to the pipe. /// /// The buffer to write data from. /// The byte offset in from which to begin writing. /// The number of bytes to write, starting at offset public void Write(byte[] buffer, int offset, int length) { CheckWriteParams(buffer, offset, length); LogLastWrite(buffer, offset, length); try { _device.InternalDevice.WritePipe(Interface.InterfaceIndex, _pipeInfo.PipeId, buffer, offset, length); } catch (API.APIException e) { byte[] SubBuffer = new byte[(length < 16 ? length : 16)]; Array.Copy(buffer, offset, SubBuffer, 0, (length < 16 ? length : 16)); // throw new USBException("Failed to write to pipe: " + WPinternals.Converter.ConvertHexToString(SubBuffer, ""), e); LogAndThrowException(new USBException("Failed to write to pipe: " + WPinternals.Converter.ConvertHexToString(SubBuffer, ""), e)); } } /// Initiates an asynchronous write operation on the pipe. /// Buffer that contains the data to write to the pipe. /// Byte offset within the buffer from which to begin writing. /// Length of the data to transfer. /// An optional asynchronous callback, to be called when the operation is complete. Can be null if no callback is required. /// A user-provided object that distinguishes this particular asynchronous operation. Can be null if not required. /// An object repesenting the asynchronous operation, which could still be pending. /// This method always completes immediately even if the operation is still pending. The object returned represents the operation /// and must be passed to to retrieve the result of the operation. For every call to this method a matching call to /// must be made. When specifies a callback function, this function will be called when the operation is completed. The optional /// parameter can be used to pass user-defined information to this callback or the . The /// also provides an event handle () that will be triggered when the operation is complete as well. /// public IAsyncResult BeginWrite(byte[] buffer, int offset, int length, AsyncCallback userCallback, object stateObject) { CheckWriteParams(buffer, offset, length); LogLastWrite(buffer, offset, length); USBAsyncResult result = new USBAsyncResult(userCallback, stateObject); try { _device.InternalDevice.WriteOverlapped(Interface.InterfaceIndex, _pipeInfo.PipeId, buffer, offset, length, result); } catch (API.APIException e) { if (result != null) result.Dispose(); byte[] SubBuffer = new byte[(length < 16 ? length : 16)]; Array.Copy(buffer, offset, SubBuffer, 0, (length < 16 ? length : 16)); // throw new USBException("Failed to write to pipe: " + WPinternals.Converter.ConvertHexToString(SubBuffer, ""), e); LogAndThrowException(new USBException("Failed to write to pipe: " + WPinternals.Converter.ConvertHexToString(SubBuffer, ""), e)); } catch (Exception e) { if (result != null) result.Dispose(); // throw; LogException(e); throw; } return result; } /// /// Waits for a pending asynchronous write operation to complete. /// /// The object representing the asynchonous operation, /// as returned by . /// The number of bytes transfered during the operation. /// Every call to must have a matching call to to dispose /// of any resources used and to retrieve the result of the operation. When the operation was successful the method returns the number /// of bytes that were transfered. If an error occurred during the operation this method will throw the exceptions that would /// otherwise have ocurred during the operation. If the operation is not yet finished EndWrite will wait for the /// operation to finish before returning. public void EndWrite(IAsyncResult asyncResult) { if (asyncResult == null) // throw new NullReferenceException("asyncResult cannot be null"); LogAndThrowException(new NullReferenceException("asyncResult cannot be null")); if (!(asyncResult is USBAsyncResult)) // throw new ArgumentException("AsyncResult object was not created by calling BeginWrite on this class."); LogAndThrowException(new ArgumentException("AsyncResult object was not created by calling BeginWrite on this class.")); USBAsyncResult result = (USBAsyncResult)asyncResult; try { // todo: check duplicate end writes? if (!result.IsCompleted) result.AsyncWaitHandle.WaitOne(); if (result.Error != null) // throw new USBException("Asynchronous write to pipe has failed.", result.Error); LogAndThrowException(new USBException("Asynchronous write to pipe has failed.", result.Error)); } finally { result.Dispose(); } } /// /// Aborts all pending transfers for this pipe. /// public void Abort() { try { _device.InternalDevice.AbortPipe(Interface.InterfaceIndex, _pipeInfo.PipeId); } catch (API.APIException e) { // throw new USBException("Failed to abort pipe.", e); LogAndThrowException(new USBException("Failed to abort pipe.", e)); } } /// /// Flushes the pipe, discarding any data that is cached. Only available on IN direction pipes. /// public void Flush() { if (!IsIn) throw new NotSupportedException("Flush is only supported on IN direction pipes"); try { _device.InternalDevice.FlushPipe(Interface.InterfaceIndex, _pipeInfo.PipeId); } catch (API.APIException e) { // throw new USBException("Failed to flush pipe.", e); LogAndThrowException(new USBException("Failed to flush pipe.", e)); } } internal USBPipe(USBDevice device, API.WINUSB_PIPE_INFORMATION pipeInfo) { _pipeInfo = pipeInfo; _device = device; // Policy is not set until interface is attached _policy = null; } internal void AttachInterface(USBInterface usbInterface) { _interface = usbInterface; // Initialize policy now that interface is set (policy requires interface) _policy = new USBPipePolicy(_device, _interface.InterfaceIndex, _pipeInfo.PipeId); } private void LogException(Exception Ex) { WPinternals.LogFile.Log("Error on USB port!", WPinternals.LogType.FileOnly); WPinternals.LogFile.Log("Device: " + _device.Descriptor.FullName, WPinternals.LogType.FileOnly); if (IsIn) LastWritten = _device.OutputPipe.LastWritten; if ((LastWritten == null) && (Ex is USBException) && (Ex.InnerException is MadWizard.WinUSBNet.API.APIException) && (((MadWizard.WinUSBNet.API.APIException)Ex.InnerException).InnerException is System.ComponentModel.Win32Exception) && (((System.ComponentModel.Win32Exception)Ex.InnerException.InnerException).NativeErrorCode == 0X1F)) WPinternals.LogFile.Log("Failed to communicate on new USB connection", WPinternals.LogType.FileAndConsole); if (LastWritten != null) WPinternals.LogFile.Log("Last written: " + WPinternals.Converter.ConvertHexToString(LastWritten, ""), WPinternals.LogType.FileOnly); WPinternals.LogFile.LogException(Ex, WPinternals.LogType.FileOnly); } private void LogAndThrowException(Exception Ex) { LogException(Ex); throw Ex; } private void LogLastWrite(byte[] Buffer, int Offset, int Length) { LastWritten = new byte[Length < 0x10 ? Length : 0x10]; System.Buffer.BlockCopy(Buffer, Offset, LastWritten, 0, LastWritten.Length); } } }