/* 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;
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 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; }
///
/// 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
{
Device.InternalDevice.ReadPipe(Interface.InterfaceIndex, _pipeInfo.PipeId, buffer, offset, length, out uint 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 ArgumentOutOfRangeException("Offset of data to read is outside the buffer boundaries.");
LogAndThrowException(new ArgumentOutOfRangeException("Offset of data to read is outside the buffer boundaries."));
}
int bufferLength = buffer.Length;
if (offset < 0 || offset >= bufferLength)
{
// throw new ArgumentOutOfRangeException(nameof(offset), "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(nameof(length), "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(nameof(offset), "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(nameof(length), "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 representing 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(userCallback, stateObject);
try
{
Device.InternalDevice.ReadPipeOverlapped(Interface.InterfaceIndex, _pipeInfo.PipeId, buffer, offset, length, result);
}
catch (API.APIException e)
{
result.Dispose();
//throw new USBException("Failed to read from pipe.", e);
LogAndThrowException(new USBException("Failed to read from pipe.", e));
}
catch (Exception e)
{
result?.Dispose();
// throw;
LogException(e);
throw;
}
return result;
}
///
/// Waits for a pending asynchronous read operation to complete.
///
/// The object representing the asynchronous 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 occurred 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.HelperClasses.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 representing 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(userCallback, stateObject);
try
{
Device.InternalDevice.WriteOverlapped(Interface.InterfaceIndex, _pipeInfo.PipeId, buffer, offset, length, result);
}
catch (API.APIException e)
{
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.HelperClasses.Converter.ConvertHexToString(SubBuffer, ""), e));
}
catch (Exception e)
{
result?.Dispose();
// throw;
LogException(e);
throw;
}
return result;
}
///
/// Waits for a pending asynchronous write operation to complete.
///
/// The object representing the asynchronous 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 occurred 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));
}
}
///
/// Resets all pending transfers for this pipe.
///
public void Reset()
{
try
{
Device.InternalDevice.ResetPipe(Interface.InterfaceIndex, _pipeInfo.PipeId);
}
catch (API.APIException e)
{
// throw new USBException("Failed to reset pipe.", e);
LogAndThrowException(new USBException("Failed to reset 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)
{
// TODO: FIX!
//WPinternals.HelperClasses.LogFile.Log("Error on USB port!", WPinternals.HelperClasses.LogType.FileOnly);
//WPinternals.HelperClasses.LogFile.Log("Device: " + Device.Descriptor.FullName, WPinternals.HelperClasses.LogType.FileOnly);
if (IsIn)
{
LastWritten = Device.OutputPipe.LastWritten;
}
if ((LastWritten == null) && (Ex is USBException) && (Ex.InnerException is API.APIException) &&
(((API.APIException)Ex.InnerException).InnerException is System.ComponentModel.Win32Exception) &&
(((System.ComponentModel.Win32Exception)Ex.InnerException.InnerException).NativeErrorCode == 0X1F))
{
//WPinternals.HelperClasses.LogFile.Log("Failed to communicate on new USB connection", WPinternals.HelperClasses.LogType.FileAndConsole);
}
if (LastWritten != null)
{
//WPinternals.HelperClasses.LogFile.Log("Last written: " + WPinternals.HelperClasses.Converter.ConvertHexToString(LastWritten, ""), WPinternals.HelperClasses.LogType.FileOnly);
}
//WPinternals.HelperClasses.LogFile.LogException(Ex, WPinternals.HelperClasses.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);
}
}
}