1 use std::ffi::c_void; 2 use std::fmt; 3 use std::fs::File; 4 use std::io; 5 use std::mem::size_of; 6 use std::os::windows::io::AsRawHandle; 7 8 use windows_sys::Wdk::Storage::FileSystem::NtCancelIoFileEx; 9 use windows_sys::Wdk::System::IO::NtDeviceIoControlFile; 10 use windows_sys::Win32::Foundation::{ 11 RtlNtStatusToDosError, HANDLE, NTSTATUS, STATUS_NOT_FOUND, STATUS_PENDING, STATUS_SUCCESS, 12 }; 13 use windows_sys::Win32::System::IO::{IO_STATUS_BLOCK, IO_STATUS_BLOCK_0}; 14 15 const IOCTL_AFD_POLL: u32 = 0x00012024; 16 17 /// Winsock2 AFD driver instance. 18 /// 19 /// All operations are unsafe due to IO_STATUS_BLOCK parameter are being used by Afd driver during STATUS_PENDING before I/O Completion Port returns its result. 20 #[derive(Debug)] 21 pub struct Afd { 22 fd: File, 23 } 24 25 #[repr(C)] 26 #[derive(Debug)] 27 pub struct AfdPollHandleInfo { 28 pub handle: HANDLE, 29 pub events: u32, 30 pub status: NTSTATUS, 31 } 32 33 unsafe impl Send for AfdPollHandleInfo {} 34 35 #[repr(C)] 36 pub struct AfdPollInfo { 37 pub timeout: i64, 38 // Can have only value 1. 39 pub number_of_handles: u32, 40 pub exclusive: u32, 41 pub handles: [AfdPollHandleInfo; 1], 42 } 43 44 impl fmt::Debug for AfdPollInfo { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 46 f.debug_struct("AfdPollInfo").finish() 47 } 48 } 49 50 impl Afd { 51 /// Poll `Afd` instance with `AfdPollInfo`. 52 /// 53 /// # Unsafety 54 /// 55 /// This function is unsafe due to memory of `IO_STATUS_BLOCK` still being used by `Afd` instance while `Ok(false)` (`STATUS_PENDING`). 56 /// `iosb` needs to be untouched after the call while operation is in effective at ALL TIME except for `cancel` method. 57 /// So be careful not to `poll` twice while polling. 58 /// User should deallocate there overlapped value when error to prevent memory leak. poll( &self, info: &mut AfdPollInfo, iosb: *mut IO_STATUS_BLOCK, overlapped: *mut c_void, ) -> io::Result<bool>59 pub unsafe fn poll( 60 &self, 61 info: &mut AfdPollInfo, 62 iosb: *mut IO_STATUS_BLOCK, 63 overlapped: *mut c_void, 64 ) -> io::Result<bool> { 65 let info_ptr = info as *mut _ as *mut c_void; 66 (*iosb).Anonymous.Status = STATUS_PENDING; 67 let status = NtDeviceIoControlFile( 68 self.fd.as_raw_handle() as HANDLE, 69 0, 70 None, 71 overlapped, 72 iosb, 73 IOCTL_AFD_POLL, 74 info_ptr, 75 size_of::<AfdPollInfo>() as u32, 76 info_ptr, 77 size_of::<AfdPollInfo>() as u32, 78 ); 79 match status { 80 STATUS_SUCCESS => Ok(true), 81 STATUS_PENDING => Ok(false), 82 _ => Err(io::Error::from_raw_os_error( 83 RtlNtStatusToDosError(status) as i32 84 )), 85 } 86 } 87 88 /// Cancel previous polled request of `Afd`. 89 /// 90 /// iosb needs to be used by `poll` first for valid `cancel`. 91 /// 92 /// # Unsafety 93 /// 94 /// This function is unsafe due to memory of `IO_STATUS_BLOCK` still being used by `Afd` instance while `Ok(false)` (`STATUS_PENDING`). 95 /// Use it only with request is still being polled so that you have valid `IO_STATUS_BLOCK` to use. 96 /// User should NOT deallocate there overlapped value after the `cancel` to prevent double free. cancel(&self, iosb: *mut IO_STATUS_BLOCK) -> io::Result<()>97 pub unsafe fn cancel(&self, iosb: *mut IO_STATUS_BLOCK) -> io::Result<()> { 98 if (*iosb).Anonymous.Status != STATUS_PENDING { 99 return Ok(()); 100 } 101 102 let mut cancel_iosb = IO_STATUS_BLOCK { 103 Anonymous: IO_STATUS_BLOCK_0 { Status: 0 }, 104 Information: 0, 105 }; 106 let status = NtCancelIoFileEx(self.fd.as_raw_handle() as HANDLE, iosb, &mut cancel_iosb); 107 if status == STATUS_SUCCESS || status == STATUS_NOT_FOUND { 108 return Ok(()); 109 } 110 Err(io::Error::from_raw_os_error( 111 RtlNtStatusToDosError(status) as i32 112 )) 113 } 114 } 115 116 cfg_io_source! { 117 use std::mem::zeroed; 118 use std::os::windows::io::{FromRawHandle, RawHandle}; 119 use std::ptr::null_mut; 120 use std::sync::atomic::{AtomicUsize, Ordering}; 121 122 use windows_sys::Wdk::Foundation::OBJECT_ATTRIBUTES; 123 use windows_sys::Wdk::Storage::FileSystem::{NtCreateFile, FILE_OPEN}; 124 use windows_sys::Win32::Foundation::{INVALID_HANDLE_VALUE, UNICODE_STRING}; 125 use windows_sys::Win32::Storage::FileSystem::{ 126 SetFileCompletionNotificationModes, FILE_SHARE_READ, FILE_SHARE_WRITE, SYNCHRONIZE, 127 }; 128 use windows_sys::Win32::System::WindowsProgramming::FILE_SKIP_SET_EVENT_ON_HANDLE; 129 130 use super::iocp::CompletionPort; 131 132 const AFD_HELPER_ATTRIBUTES: OBJECT_ATTRIBUTES = OBJECT_ATTRIBUTES { 133 Length: size_of::<OBJECT_ATTRIBUTES>() as u32, 134 RootDirectory: 0, 135 ObjectName: &AFD_OBJ_NAME as *const _ as *mut _, 136 Attributes: 0, 137 SecurityDescriptor: null_mut(), 138 SecurityQualityOfService: null_mut(), 139 }; 140 141 const AFD_OBJ_NAME: UNICODE_STRING = UNICODE_STRING { 142 Length: (AFD_HELPER_NAME.len() * size_of::<u16>()) as u16, 143 MaximumLength: (AFD_HELPER_NAME.len() * size_of::<u16>()) as u16, 144 Buffer: AFD_HELPER_NAME.as_ptr() as *mut _, 145 }; 146 147 const AFD_HELPER_NAME: &[u16] = &[ 148 '\\' as _, 149 'D' as _, 150 'e' as _, 151 'v' as _, 152 'i' as _, 153 'c' as _, 154 'e' as _, 155 '\\' as _, 156 'A' as _, 157 'f' as _, 158 'd' as _, 159 '\\' as _, 160 'M' as _, 161 'i' as _, 162 'o' as _ 163 ]; 164 165 static NEXT_TOKEN: AtomicUsize = AtomicUsize::new(0); 166 167 impl AfdPollInfo { 168 pub fn zeroed() -> AfdPollInfo { 169 unsafe { zeroed() } 170 } 171 } 172 173 impl Afd { 174 /// Create new Afd instance. 175 pub(crate) fn new(cp: &CompletionPort) -> io::Result<Afd> { 176 let mut afd_helper_handle: HANDLE = INVALID_HANDLE_VALUE; 177 let mut iosb = IO_STATUS_BLOCK { 178 Anonymous: IO_STATUS_BLOCK_0 { Status: 0 }, 179 Information: 0, 180 }; 181 182 unsafe { 183 let status = NtCreateFile( 184 &mut afd_helper_handle as *mut _, 185 SYNCHRONIZE, 186 &AFD_HELPER_ATTRIBUTES as *const _ as *mut _, 187 &mut iosb, 188 null_mut(), 189 0, 190 FILE_SHARE_READ | FILE_SHARE_WRITE, 191 FILE_OPEN, 192 0, 193 null_mut(), 194 0, 195 ); 196 if status != STATUS_SUCCESS { 197 let raw_err = io::Error::from_raw_os_error( 198 RtlNtStatusToDosError(status) as i32 199 ); 200 let msg = format!("Failed to open \\Device\\Afd\\Mio: {}", raw_err); 201 return Err(io::Error::new(raw_err.kind(), msg)); 202 } 203 let fd = File::from_raw_handle(afd_helper_handle as RawHandle); 204 // Increment by 2 to reserve space for other types of handles. 205 // Non-AFD types (currently only NamedPipe), use odd numbered 206 // tokens. This allows the selector to differentiate between them 207 // and dispatch events accordingly. 208 let token = NEXT_TOKEN.fetch_add(2, Ordering::Relaxed) + 2; 209 let afd = Afd { fd }; 210 cp.add_handle(token, &afd.fd)?; 211 match SetFileCompletionNotificationModes( 212 afd_helper_handle, 213 FILE_SKIP_SET_EVENT_ON_HANDLE as u8 // This is just 2, so fits in u8 214 ) { 215 0 => Err(io::Error::last_os_error()), 216 _ => Ok(afd), 217 } 218 } 219 } 220 } 221 } 222 223 pub const POLL_RECEIVE: u32 = 0b0_0000_0001; 224 pub const POLL_RECEIVE_EXPEDITED: u32 = 0b0_0000_0010; 225 pub const POLL_SEND: u32 = 0b0_0000_0100; 226 pub const POLL_DISCONNECT: u32 = 0b0_0000_1000; 227 pub const POLL_ABORT: u32 = 0b0_0001_0000; 228 pub const POLL_LOCAL_CLOSE: u32 = 0b0_0010_0000; 229 // Not used as it indicated in each event where a connection is connected, not 230 // just the first time a connection is established. 231 // Also see https://github.com/piscisaureus/wepoll/commit/8b7b340610f88af3d83f40fb728e7b850b090ece. 232 pub const POLL_CONNECT: u32 = 0b0_0100_0000; 233 pub const POLL_ACCEPT: u32 = 0b0_1000_0000; 234 pub const POLL_CONNECT_FAIL: u32 = 0b1_0000_0000; 235 236 pub const KNOWN_EVENTS: u32 = POLL_RECEIVE 237 | POLL_RECEIVE_EXPEDITED 238 | POLL_SEND 239 | POLL_DISCONNECT 240 | POLL_ABORT 241 | POLL_LOCAL_CLOSE 242 | POLL_ACCEPT 243 | POLL_CONNECT_FAIL; 244