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