Skip to content

Commit 38df267

Browse files
authored
add test for opening and reading from device interface (#54673)
1 parent 5b58501 commit 38df267

File tree

2 files changed

+148
-3
lines changed

2 files changed

+148
-3
lines changed

src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.Windows.cs

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
using Microsoft.Win32.SafeHandles;
55
using System.ComponentModel;
66
using System.IO.Pipes;
7-
using System.Linq;
87
using System.Runtime.CompilerServices;
98
using System.Runtime.InteropServices;
109
using System.Text;
1110
using System.ServiceProcess;
1211
using System.Threading.Tasks;
1312
using Xunit;
13+
using System.Threading;
1414

1515
namespace System.IO.Tests
1616
{
@@ -176,4 +176,148 @@ public struct SHARE_INFO_502
176176
[DllImport(Interop.Libraries.Netapi32)]
177177
public static extern int NetShareDel([MarshalAs(UnmanagedType.LPWStr)] string servername, [MarshalAs(UnmanagedType.LPWStr)] string netname, int reserved);
178178
}
179+
180+
[PlatformSpecific(TestPlatforms.Windows)] // the test setup is Windows-specifc
181+
[OuterLoop("Has a very complex setup logic that in theory might have some side-effects")]
182+
[ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))]
183+
[ActiveIssue("https://github.com/dotnet/runtime/issues/34582", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)]
184+
public class DeviceInterfaceTests
185+
{
186+
[Fact]
187+
public async Task DeviceInterfaceCanBeOpenedForAsyncIO()
188+
{
189+
FileStream? fileStream = OpenFirstAvailableDeviceInterface();
190+
191+
if (fileStream is null)
192+
{
193+
// it's OK to not have any such devices available
194+
// this test is just best effort
195+
return;
196+
}
197+
198+
using (fileStream)
199+
{
200+
Assert.True(fileStream.CanRead);
201+
Assert.False(fileStream.CanWrite);
202+
Assert.False(fileStream.CanSeek); // #54143
203+
204+
try
205+
{
206+
CancellationTokenSource cts = new(TimeSpan.FromMilliseconds(250));
207+
208+
await fileStream.ReadAsync(new byte[4096], cts.Token);
209+
}
210+
catch (OperationCanceledException)
211+
{
212+
// most likely there is no data available and the task is going to get cancelled
213+
// which is fine, we just want to make sure that reading from devices is supported (#54143)
214+
}
215+
}
216+
}
217+
218+
private static FileStream? OpenFirstAvailableDeviceInterface()
219+
{
220+
const int DIGCF_PRESENT = 0x2;
221+
const int DIGCF_DEVICEINTERFACE = 0x10;
222+
const int ERROR_NO_MORE_ITEMS = 259;
223+
224+
HidD_GetHidGuid(out Guid HidGuid);
225+
IntPtr deviceInfoSet = SetupDiGetClassDevs(in HidGuid, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
226+
227+
try
228+
{
229+
SP_DEVINFO_DATA deviceInfoData = new SP_DEVINFO_DATA();
230+
deviceInfoData.cbSize = (uint)Marshal.SizeOf(deviceInfoData);
231+
232+
uint deviceIndex = 0;
233+
while (SetupDiEnumDeviceInfo(deviceInfoSet, deviceIndex++, ref deviceInfoData))
234+
{
235+
if (Marshal.GetLastWin32Error() == ERROR_NO_MORE_ITEMS)
236+
{
237+
break;
238+
}
239+
240+
SP_DEVICE_INTERFACE_DATA deviceInterfaceData = new SP_DEVICE_INTERFACE_DATA();
241+
deviceInterfaceData.cbSize = Marshal.SizeOf(deviceInterfaceData);
242+
243+
if (!SetupDiEnumDeviceInterfaces(deviceInfoSet, IntPtr.Zero, in HidGuid, deviceIndex, ref deviceInterfaceData))
244+
{
245+
continue;
246+
}
247+
248+
SP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = new SP_DEVICE_INTERFACE_DETAIL_DATA();
249+
deviceInterfaceDetailData.cbSize = IntPtr.Size == 8 ? 8 : 6;
250+
251+
uint size = (uint)Marshal.SizeOf(deviceInterfaceDetailData);
252+
253+
if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref deviceInterfaceData, ref deviceInterfaceDetailData, size, ref size, IntPtr.Zero))
254+
{
255+
continue;
256+
}
257+
258+
string devicePath = deviceInterfaceDetailData.DevicePath;
259+
Assert.StartsWith(@"\\?\hid", devicePath);
260+
261+
try
262+
{
263+
return new FileStream(devicePath, FileMode.Open, FileAccess.Read, FileShare.Read, 0, FileOptions.Asynchronous);
264+
}
265+
catch (IOException)
266+
{
267+
continue; // device has been locked by another process
268+
}
269+
}
270+
}
271+
finally
272+
{
273+
SetupDiDestroyDeviceInfoList(deviceInfoSet);
274+
}
275+
276+
return null;
277+
}
278+
279+
[StructLayout(LayoutKind.Sequential)]
280+
struct SP_DEVICE_INTERFACE_DATA
281+
{
282+
public int cbSize;
283+
public Guid interfaceClassGuid;
284+
public int flags;
285+
private nuint reserved;
286+
}
287+
288+
[StructLayout(LayoutKind.Sequential)]
289+
struct SP_DEVINFO_DATA
290+
{
291+
public uint cbSize;
292+
public Guid ClassGuid;
293+
public uint DevInst;
294+
public nint Reserved;
295+
}
296+
297+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
298+
struct SP_DEVICE_INTERFACE_DETAIL_DATA
299+
{
300+
public int cbSize;
301+
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] // 256 should be always enough for device interface path
302+
public string DevicePath;
303+
}
304+
305+
[DllImport("hid.dll", SetLastError = true)]
306+
static extern void HidD_GetHidGuid(out Guid HidGuid);
307+
308+
[DllImport("setupapi.dll", SetLastError = true)]
309+
static extern IntPtr SetupDiGetClassDevs(in Guid ClassGuid, IntPtr Enumerator, IntPtr hwndParent, int Flags);
310+
311+
[DllImport("setupapi.dll", SetLastError = true)]
312+
static extern bool SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet, uint MemberIndex, ref SP_DEVINFO_DATA DeviceInfoData);
313+
314+
[DllImport("setupapi.dll", SetLastError = true)]
315+
static extern bool SetupDiEnumDeviceInterfaces(IntPtr DeviceInfoSet, IntPtr DeviceInfoData, in Guid InterfaceClassGuid, uint MemberIndex, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData);
316+
317+
[DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
318+
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);
319+
320+
[DllImport("setupapi.dll", SetLastError = true)]
321+
static extern bool SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);
322+
}
179323
}

src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
44
<IncludeRemoteExecutor>true</IncludeRemoteExecutor>
@@ -19,13 +19,14 @@
1919
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">
2020
<Compile Remove="..\**\*.Unix.cs" />
2121
<Compile Remove="..\**\*.Browser.cs" />
22+
<!-- .NET 5 did not support async file IO for device interfaces -->
23+
<Compile Remove="..\FileStream\FileStreamConformanceTests.Windows.cs" />
2224
<Compile Include="$(CommonPath)Interop\Windows\Interop.BOOL.cs" Link="Common\Interop\Windows\Interop.BOOL.cs" />
2325
<Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs" Link="Common\Interop\Windows\Interop.Libraries.cs" />
2426
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.FILE_STANDARD_INFO.cs" Link="Common\Interop\Windows\Interop.FILE_STANDARD_INFO.cs" />
2527
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetFileInformationByHandleEx.cs" Link="Common\Interop\Windows\Interop.GetFileInformationByHandleEx.cs" />
2628
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.MemOptions.cs" Link="Common\Interop\Windows\Interop.MemOptions.cs" />
2729
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.VirtualAlloc_Ptr.cs" Link="Common\Interop\Windows\Interop.VirtualAlloc_Ptr.cs" />
28-
<ProjectReference Include="$(LibrariesProjectRoot)System.ServiceProcess.ServiceController\src\System.ServiceProcess.ServiceController.csproj" />
2930
</ItemGroup>
3031
<ItemGroup Condition="'$(TargetsBrowser)' == 'true'">
3132
<Compile Remove="..\**\*.Unix.cs" />

0 commit comments

Comments
 (0)