|
4 | 4 | using Microsoft.Win32.SafeHandles;
|
5 | 5 | using System.ComponentModel;
|
6 | 6 | using System.IO.Pipes;
|
7 |
| -using System.Linq; |
8 | 7 | using System.Runtime.CompilerServices;
|
9 | 8 | using System.Runtime.InteropServices;
|
10 | 9 | using System.Text;
|
11 | 10 | using System.ServiceProcess;
|
12 | 11 | using System.Threading.Tasks;
|
13 | 12 | using Xunit;
|
| 13 | +using System.Threading; |
14 | 14 |
|
15 | 15 | namespace System.IO.Tests
|
16 | 16 | {
|
@@ -176,4 +176,148 @@ public struct SHARE_INFO_502
|
176 | 176 | [DllImport(Interop.Libraries.Netapi32)]
|
177 | 177 | public static extern int NetShareDel([MarshalAs(UnmanagedType.LPWStr)] string servername, [MarshalAs(UnmanagedType.LPWStr)] string netname, int reserved);
|
178 | 178 | }
|
| 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 | + } |
179 | 323 | }
|
0 commit comments