using Microsoft.Win32.SafeHandles;
using System.ComponentModel;
using System.IO.Pipes;
-using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.ServiceProcess;
using System.Threading.Tasks;
using Xunit;
+using System.Threading;
namespace System.IO.Tests
{
[DllImport(Interop.Libraries.Netapi32)]
public static extern int NetShareDel([MarshalAs(UnmanagedType.LPWStr)] string servername, [MarshalAs(UnmanagedType.LPWStr)] string netname, int reserved);
}
+
+ [PlatformSpecific(TestPlatforms.Windows)] // the test setup is Windows-specifc
+ [OuterLoop("Has a very complex setup logic that in theory might have some side-effects")]
+ [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
+ public class DeviceInterfaceTests
+ {
+ [Fact]
+ public async Task DeviceInterfaceCanBeOpenedForAsyncIO()
+ {
+ FileStream? fileStream = OpenFirstAvailableDeviceInterface();
+
+ if (fileStream is null)
+ {
+ // it's OK to not have any such devices available
+ // this test is just best effort
+ return;
+ }
+
+ using (fileStream)
+ {
+ Assert.True(fileStream.CanRead);
+ Assert.False(fileStream.CanWrite);
+ Assert.False(fileStream.CanSeek); // #54143
+
+ try
+ {
+ CancellationTokenSource cts = new(TimeSpan.FromMilliseconds(250));
+
+ await fileStream.ReadAsync(new byte[4096], cts.Token);
+ }
+ catch (OperationCanceledException)
+ {
+ // most likely there is no data available and the task is going to get cancelled
+ // which is fine, we just want to make sure that reading from devices is supported (#54143)
+ }
+ }
+ }
+
+ private static FileStream? OpenFirstAvailableDeviceInterface()
+ {
+ const int DIGCF_PRESENT = 0x2;
+ const int DIGCF_DEVICEINTERFACE = 0x10;
+ const int ERROR_NO_MORE_ITEMS = 259;
+
+ HidD_GetHidGuid(out Guid HidGuid);
+ IntPtr deviceInfoSet = SetupDiGetClassDevs(in HidGuid, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
+
+ try
+ {
+ SP_DEVINFO_DATA deviceInfoData = new SP_DEVINFO_DATA();
+ deviceInfoData.cbSize = (uint)Marshal.SizeOf(deviceInfoData);
+
+ uint deviceIndex = 0;
+ while (SetupDiEnumDeviceInfo(deviceInfoSet, deviceIndex++, ref deviceInfoData))
+ {
+ if (Marshal.GetLastWin32Error() == ERROR_NO_MORE_ITEMS)
+ {
+ break;
+ }
+
+ SP_DEVICE_INTERFACE_DATA deviceInterfaceData = new SP_DEVICE_INTERFACE_DATA();
+ deviceInterfaceData.cbSize = Marshal.SizeOf(deviceInterfaceData);
+
+ if (!SetupDiEnumDeviceInterfaces(deviceInfoSet, IntPtr.Zero, in HidGuid, deviceIndex, ref deviceInterfaceData))
+ {
+ continue;
+ }
+
+ SP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = new SP_DEVICE_INTERFACE_DETAIL_DATA();
+ deviceInterfaceDetailData.cbSize = IntPtr.Size == 8 ? 8 : 6;
+
+ uint size = (uint)Marshal.SizeOf(deviceInterfaceDetailData);
+
+ if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref deviceInterfaceData, ref deviceInterfaceDetailData, size, ref size, IntPtr.Zero))
+ {
+ continue;
+ }
+
+ string devicePath = deviceInterfaceDetailData.DevicePath;
+ Assert.StartsWith(@"\\?\hid", devicePath);
+
+ try
+ {
+ return new FileStream(devicePath, FileMode.Open, FileAccess.Read, FileShare.Read, 0, FileOptions.Asynchronous);
+ }
+ catch (IOException)
+ {
+ continue; // device has been locked by another process
+ }
+ }
+ }
+ finally
+ {
+ SetupDiDestroyDeviceInfoList(deviceInfoSet);
+ }
+
+ return null;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ struct SP_DEVICE_INTERFACE_DATA
+ {
+ public int cbSize;
+ public Guid interfaceClassGuid;
+ public int flags;
+ private nuint reserved;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ struct SP_DEVINFO_DATA
+ {
+ public uint cbSize;
+ public Guid ClassGuid;
+ public uint DevInst;
+ public nint Reserved;
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ struct SP_DEVICE_INTERFACE_DETAIL_DATA
+ {
+ public int cbSize;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] // 256 should be always enough for device interface path
+ public string DevicePath;
+ }
+
+ [DllImport("hid.dll", SetLastError = true)]
+ static extern void HidD_GetHidGuid(out Guid HidGuid);
+
+ [DllImport("setupapi.dll", SetLastError = true)]
+ static extern IntPtr SetupDiGetClassDevs(in Guid ClassGuid, IntPtr Enumerator, IntPtr hwndParent, int Flags);
+
+ [DllImport("setupapi.dll", SetLastError = true)]
+ static extern bool SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet, uint MemberIndex, ref SP_DEVINFO_DATA DeviceInfoData);
+
+ [DllImport("setupapi.dll", SetLastError = true)]
+ static extern bool SetupDiEnumDeviceInterfaces(IntPtr DeviceInfoSet, IntPtr DeviceInfoData, in Guid InterfaceClassGuid, uint MemberIndex, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData);
+
+ [DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, ref SP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData, uint DeviceInterfaceDetailDataSize, ref uint RequiredSize, IntPtr DeviceInfoData);
+
+ [DllImport("setupapi.dll", SetLastError = true)]
+ static extern bool SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);
+ }
}
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IncludeRemoteExecutor>true</IncludeRemoteExecutor>
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">
<Compile Remove="..\**\*.Unix.cs" />
<Compile Remove="..\**\*.Browser.cs" />
+ <!-- .NET 5 did not support async file IO for device interfaces -->
+ <Compile Remove="..\FileStream\FileStreamConformanceTests.Windows.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Interop.BOOL.cs" Link="Common\Interop\Windows\Interop.BOOL.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs" Link="Common\Interop\Windows\Interop.Libraries.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.FILE_STANDARD_INFO.cs" Link="Common\Interop\Windows\Interop.FILE_STANDARD_INFO.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetFileInformationByHandleEx.cs" Link="Common\Interop\Windows\Interop.GetFileInformationByHandleEx.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.MemOptions.cs" Link="Common\Interop\Windows\Interop.MemOptions.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.VirtualAlloc_Ptr.cs" Link="Common\Interop\Windows\Interop.VirtualAlloc_Ptr.cs" />
- <ProjectReference Include="$(LibrariesProjectRoot)System.ServiceProcess.ServiceController\src\System.ServiceProcess.ServiceController.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsBrowser)' == 'true'">
<Compile Remove="..\**\*.Unix.cs" />