Skip to content
This repository has been archived by the owner on Jul 26, 2023. It is now read-only.

Commit

Permalink
Merge pull request #513 from qmfrederik/features/winusb-async
Browse files Browse the repository at this point in the history
Add WinUsb_WritePipeAsync, WinUsb_ReadPipeAsync
  • Loading branch information
AArnott authored Jul 17, 2020
2 parents cc67461 + fcea500 commit c493509
Show file tree
Hide file tree
Showing 5 changed files with 427 additions and 0 deletions.
150 changes: 150 additions & 0 deletions src/WinUsb.Tests/WinUsbFacts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Copyright © .NET Foundation and Contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
using static PInvoke.Kernel32;
using static PInvoke.WinUsb;
using Win32ErrorCode = PInvoke.Win32ErrorCode;

public partial class WinUsbFacts
{
[Fact]
public async Task WinUsb_ReadPipeAsync_Exception_Test()
{
await Assert.ThrowsAsync<PInvoke.Win32Exception>(() => WinUsb_ReadPipeAsync(new SafeUsbHandle(IntPtr.Zero), 0, Array.Empty<byte>(), default).AsTask()).ConfigureAwait(false);
}

[Fact(Skip = "Requires USB device")]
public async Task WinUsb_ReadPipeAsync_Overlapped_Test()
{
var devicePath = @"<path to your USB device>";

using (var handle = CreateFile(
devicePath,
ACCESS_MASK.GenericRight.GENERIC_READ | ACCESS_MASK.GenericRight.GENERIC_WRITE,
FileShare.FILE_SHARE_READ | FileShare.FILE_SHARE_WRITE,
IntPtr.Zero,
CreationDisposition.OPEN_EXISTING,
CreateFileFlags.FILE_FLAG_OVERLAPPED,
SafeObjectHandle.Null))
{
ThreadPool.BindHandle(handle);

if (!WinUsb_Initialize(handle, out SafeUsbHandle usbHandle))
{
throw new PInvoke.Win32Exception(Marshal.GetLastWin32Error());
}

Assert.True(WinUsb_QueryPipe(usbHandle, alternateInterfaceNumber: 0, pipeIndex: 0, out WINUSB_PIPE_INFORMATION input));

var readTask = WinUsb_ReadPipeAsync(usbHandle, input.PipeId, Array.Empty<byte>(), default);
Assert.Equal(0, await readTask.ConfigureAwait(false));
}
}

[Fact(Skip = "Requires USB device")]
public async Task WinUsb_ReadPipeAsync_Cancelled_Test()
{
var devicePath = @"<path to your USB device>";

using (var handle = CreateFile(
devicePath,
ACCESS_MASK.GenericRight.GENERIC_READ | ACCESS_MASK.GenericRight.GENERIC_WRITE,
FileShare.FILE_SHARE_READ | FileShare.FILE_SHARE_WRITE,
IntPtr.Zero,
CreationDisposition.OPEN_EXISTING,
CreateFileFlags.FILE_FLAG_OVERLAPPED,
SafeObjectHandle.Null))
{
ThreadPool.BindHandle(handle);

if (!WinUsb_Initialize(handle, out SafeUsbHandle usbHandle))
{
throw new PInvoke.Win32Exception(Marshal.GetLastWin32Error());
}

Assert.True(WinUsb_QueryPipe(usbHandle, alternateInterfaceNumber: 0, pipeIndex: 0, out WINUSB_PIPE_INFORMATION input));

byte[] buffer = new byte[100];

var cts = new CancellationTokenSource();
var readTask = WinUsb_ReadPipeAsync(usbHandle, input.PipeId, buffer, cts.Token);
cts.Cancel();

var ex = await Assert.ThrowsAsync<PInvoke.Win32Exception>(() => readTask.AsTask());
Assert.Equal(Win32ErrorCode.ERROR_OPERATION_ABORTED, ex.NativeErrorCode);
}
}

[Fact]
public async Task WinUsb_WritePipeAsync_Exception_Test()
{
await Assert.ThrowsAsync<PInvoke.Win32Exception>(() => WinUsb_WritePipeAsync(new SafeUsbHandle(IntPtr.Zero), 0, Array.Empty<byte>(), default).AsTask()).ConfigureAwait(false);
}

[Fact(Skip = "Requires USB device")]
public async Task WinUsb_WritePipeAsync_Overlapped_Test()
{
var devicePath = @"<path to your USB device>";

using (var handle = CreateFile(
devicePath,
ACCESS_MASK.GenericRight.GENERIC_READ | ACCESS_MASK.GenericRight.GENERIC_WRITE,
FileShare.FILE_SHARE_READ | FileShare.FILE_SHARE_WRITE,
IntPtr.Zero,
CreationDisposition.OPEN_EXISTING,
CreateFileFlags.FILE_FLAG_OVERLAPPED,
SafeObjectHandle.Null))
{
ThreadPool.BindHandle(handle);

if (!WinUsb_Initialize(handle, out SafeUsbHandle usbHandle))
{
throw new PInvoke.Win32Exception(Marshal.GetLastWin32Error());
}

Assert.True(WinUsb_QueryPipe(usbHandle, alternateInterfaceNumber: 0, pipeIndex: 0, out WINUSB_PIPE_INFORMATION input));

var readTask = WinUsb_WritePipeAsync(usbHandle, input.PipeId, Array.Empty<byte>(), default);
Assert.Equal(0, await readTask.ConfigureAwait(false));
}
}

[Fact(Skip = "Requires USB device")]
public async Task WinUsb_WritePipeAsync_Cancelled_Test()
{
var devicePath = @"<path to your USB device>";

using (var handle = CreateFile(
devicePath,
ACCESS_MASK.GenericRight.GENERIC_READ | ACCESS_MASK.GenericRight.GENERIC_WRITE,
FileShare.FILE_SHARE_READ | FileShare.FILE_SHARE_WRITE,
IntPtr.Zero,
CreationDisposition.OPEN_EXISTING,
CreateFileFlags.FILE_FLAG_OVERLAPPED,
SafeObjectHandle.Null))
{
ThreadPool.BindHandle(handle);

if (!WinUsb_Initialize(handle, out SafeUsbHandle usbHandle))
{
throw new PInvoke.Win32Exception(Marshal.GetLastWin32Error());
}

Assert.True(WinUsb_QueryPipe(usbHandle, alternateInterfaceNumber: 0, pipeIndex: 0, out WINUSB_PIPE_INFORMATION input));

byte[] buffer = new byte[100];

var cts = new CancellationTokenSource();
var readTask = WinUsb_WritePipeAsync(usbHandle, input.PipeId, buffer, cts.Token);
cts.Cancel();

var ex = await Assert.ThrowsAsync<PInvoke.Win32Exception>(() => readTask.AsTask());
Assert.Equal(Win32ErrorCode.ERROR_OPERATION_ABORTED, ex.NativeErrorCode);
}
}
}
2 changes: 2 additions & 0 deletions src/WinUsb/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ static PInvoke.WinUsb.WinUsb_QueryPipe(PInvoke.WinUsb.SafeUsbHandle interfaceHan
static PInvoke.WinUsb.WinUsb_QueryPipe(PInvoke.WinUsb.SafeUsbHandle interfaceHandle, byte alternateInterfaceNumber, byte pipeIndex, out PInvoke.WinUsb.WINUSB_PIPE_INFORMATION pipeInformation) -> bool
static PInvoke.WinUsb.WinUsb_ReadPipe(PInvoke.WinUsb.SafeUsbHandle interfaceHandle, byte pipeID, System.IntPtr buffer, int bufferLength, out int lengthTransferred, System.IntPtr overlapped) -> bool
static PInvoke.WinUsb.WinUsb_ReadPipe(PInvoke.WinUsb.SafeUsbHandle interfaceHandle, byte pipeID, byte[] buffer, int bufferLength, out int lengthTransferred, System.Threading.NativeOverlapped? overlapped) -> bool
static PInvoke.WinUsb.WinUsb_ReadPipeAsync(PInvoke.WinUsb.SafeUsbHandle interfaceHandle, byte pipeID, System.Memory<byte> buffer, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask<int>
static PInvoke.WinUsb.WinUsb_WritePipe(PInvoke.WinUsb.SafeUsbHandle interfaceHandle, byte pipeID, System.IntPtr buffer, int bufferLength, out int lengthTransferred, System.IntPtr overlapped) -> bool
static PInvoke.WinUsb.WinUsb_WritePipe(PInvoke.WinUsb.SafeUsbHandle interfaceHandle, byte pipeID, byte[] buffer, int bufferLength, out int lengthTransferred, System.Threading.NativeOverlapped? overlapped) -> bool
static PInvoke.WinUsb.WinUsb_WritePipeAsync(PInvoke.WinUsb.SafeUsbHandle interfaceHandle, byte pipeID, System.Memory<byte> buffer, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask<int>
static extern PInvoke.WinUsb.WinUsb_AbortPipe(PInvoke.WinUsb.SafeUsbHandle handle, byte pipeID) -> bool
static extern PInvoke.WinUsb.WinUsb_Initialize(PInvoke.Kernel32.SafeObjectHandle deviceHandle, out PInvoke.WinUsb.SafeUsbHandle interfaceHandle) -> bool
static extern PInvoke.WinUsb.WinUsb_QueryPipe(PInvoke.WinUsb.SafeUsbHandle interfaceHandle, byte alternateInterfaceNumber, byte pipeIndex, PInvoke.WinUsb.WINUSB_PIPE_INFORMATION* pipeInformation) -> bool
Expand Down
144 changes: 144 additions & 0 deletions src/WinUsb/WinUsb+WinUsbOverlapped.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// Copyright © .NET Foundation and Contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace PInvoke
{
using System;
using System.Buffers;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

/// <content>
/// Contains the nested <see cref="WinUsbOverlapped"/> type.
/// </content>
public static partial class WinUsb
{
/// <summary>
/// A managed implementation of the <see cref="Kernel32.OVERLAPPED"/> structure, used to support overlapped WinUSB I/O.
/// </summary>
private class WinUsbOverlapped : Overlapped
{
private readonly Memory<byte> buffer;
private readonly SafeUsbHandle handle;
private readonly byte pipeID;
private readonly CancellationToken cancellationToken;

/// <summary>
/// The source for completing the <see cref="Completion"/> property.
/// </summary>
private readonly TaskCompletionSource<int> completion = new TaskCompletionSource<int>();

private unsafe NativeOverlapped* native;
private CancellationTokenRegistration cancellationTokenRegistration;

/// <summary>
/// Initializes a new instance of the <see cref="WinUsbOverlapped"/> class.
/// </summary>
/// <param name="handle">
/// A handle to the WinUSB device on which the I/O is being performed.
/// </param>
/// <param name="pipeID">
/// The ID of the pipe on which the I/O is being performed.
/// </param>
/// <param name="buffer">
/// The buffer which is used by the I/O operation. This buffer will be pinned for the duration of
/// the operation.
/// </param>
/// <param name="cancellationToken">
/// A <see cref="CancellationToken"/> which can be used to cancel the overlapped I/O.
/// </param>
public WinUsbOverlapped(SafeUsbHandle handle, byte pipeID, Memory<byte> buffer, CancellationToken cancellationToken)
{
this.handle = handle ?? throw new ArgumentNullException(nameof(handle));
this.pipeID = pipeID;
this.buffer = buffer;
this.cancellationToken = cancellationToken;
}

/// <summary>
/// Gets a <see cref="MemoryHandle"/> to the transfer buffer.
/// </summary>
internal MemoryHandle BufferHandle { get; private set; }

/// <summary>
/// Gets the amount of bytes transferred.
/// </summary>
internal uint BytesTransferred { get; private set; }

/// <summary>
/// Gets the error code returned by the device driver.
/// </summary>
internal uint ErrorCode { get; private set; }

/// <summary>
/// Gets a task whose result is the number of bytes transferred, or faults with the <see cref="Win32Exception"/> describing the failure.
/// </summary>
internal Task<int> Completion => this.completion.Task;

/// <summary>
/// Packs the current <see cref="WinUsbOverlapped"/> into a <see cref="NativeOverlapped"/> structure.
/// </summary>
/// <returns>
/// An unmanaged pointer to a <see cref="NativeOverlapped"/> structure.
/// </returns>
internal unsafe NativeOverlapped* Pack()
{
this.BufferHandle = this.buffer.Pin();

this.native = this.Pack(
this.DeviceIOControlCompletionCallback,
null);

this.cancellationTokenRegistration = this.cancellationToken.Register(this.Cancel);

return this.native;
}

/// <summary>
/// Unpacks the unmanaged <see cref="NativeOverlapped"/> structure into
/// a managed <see cref="WinUsbOverlapped"/> object.
/// </summary>
internal unsafe void Unpack()
{
Overlapped.Unpack(this.native);
Overlapped.Free(this.native);
this.native = null;

this.cancellationTokenRegistration.Dispose();
this.BufferHandle.Dispose();
}

/// <summary>
/// Cancels the asynchronous I/O operation.
/// </summary>
internal unsafe void Cancel()
{
if (!WinUsb_AbortPipe(
this.handle,
this.pipeID))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}

private unsafe void DeviceIOControlCompletionCallback(uint errorCode, uint numberOfBytesTransferred, NativeOverlapped* nativeOverlapped)
{
this.Unpack();

this.BytesTransferred = numberOfBytesTransferred;
this.ErrorCode = errorCode;

if (this.ErrorCode != 0)
{
this.completion.SetException(
new Win32Exception((int)this.ErrorCode));
}
else
{
this.completion.SetResult((int)numberOfBytesTransferred);
}
}
}
}
}
Loading

0 comments on commit c493509

Please sign in to comment.