This repository has been archived by the owner on Jul 26, 2023. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 223
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #513 from qmfrederik/features/winusb-async
Add WinUsb_WritePipeAsync, WinUsb_ReadPipeAsync
- Loading branch information
Showing
5 changed files
with
427 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.