Skip to main content

windows_overlapped_io_sys/
socket.rs

1// Copyright (c) 2026 Mike Grier
2//! Safe socket operation adapters, gated behind the `socket` feature.
3//!
4//! These wrappers own the I/O buffer and the `WSABUF` describing it, issue the
5//! single native `WSARecv` / `WSASend` internally, and route completions through
6//! the same [`CompletionPort`] as the handle backends. A socket is owned as a
7//! [`OwnedSocket`] (its destructor is `closesocket`), so it gets its own
8//! endpoint type rather than reusing the handle-based `AssociatedEndpoint`.
9//!
10//! The caller provides a connected, overlapped-capable socket -- every
11//! `std::net` socket qualifies, and owning one keeps Winsock initialized -- so
12//! the crate never calls `WSAStartup` itself.
13
14use std::io;
15use std::os::windows::io::{AsRawSocket, AsSocket, BorrowedSocket, OwnedSocket};
16
17use windows_sys::Win32::Foundation::HANDLE;
18use windows_sys::Win32::Networking::WinSock::{
19    SO_PROTOCOL_INFOW, SOCKET, SOCKET_ERROR, SOL_SOCKET, WSA_INVALID_EVENT, WSA_IO_PENDING, WSABUF,
20    WSACloseEvent, WSACreateEvent, WSAEVENT, WSAGetOverlappedResult, WSAPROTOCOL_INFOW, WSARecv,
21    WSASend, XP1_IFS_HANDLES, getsockopt,
22};
23use windows_sys::Win32::System::IO::{CancelIoEx, CreateIoCompletionPort, OVERLAPPED};
24
25use crate::endpoint::notification_flags;
26use crate::operation::{payload_ptr_from_overlapped, sync_bytes_ptr_from_overlapped};
27use crate::{
28    Completion, CompletionPort, IoBuf, IoBufMut, Issued, NotificationModes, Operation, OperationId,
29    Started, Submitted,
30};
31
32impl CompletionPort {
33    /// Associate an overlapped socket with this port under `key`.
34    ///
35    /// Completions for operations issued on the socket are delivered to this port
36    /// and tagged with `key`. The socket must be overlapped-capable, which every
37    /// `std::net` socket and any `WSASocket` created with `WSA_FLAG_OVERLAPPED`
38    /// is; the association is permanent for the life of the socket.
39    ///
40    /// # Errors
41    ///
42    /// Returns any error from `CreateIoCompletionPort`.
43    pub fn associate_socket(
44        &self,
45        socket: OwnedSocket,
46        key: usize,
47    ) -> io::Result<AssociatedSocket<'_>> {
48        // SAFETY: associating a valid socket handle with a valid port; the
49        // concurrency argument is ignored when an existing port is supplied.
50        let result = unsafe {
51            CreateIoCompletionPort(
52                socket.as_raw_socket() as usize as HANDLE,
53                self.raw(),
54                key,
55                0,
56            )
57        };
58        if result.is_null() {
59            return Err(io::Error::last_os_error());
60        }
61        Ok(AssociatedSocket {
62            port: self,
63            socket,
64            key,
65            modes: NotificationModes::default(),
66        })
67    }
68}
69
70/// An overlapped socket bound to exactly one [`CompletionPort`].
71///
72/// The endpoint owns its socket (closed with `closesocket` on drop) and borrows
73/// the port it is associated with. It is intentionally not `Clone`.
74#[derive(Debug)]
75pub struct AssociatedSocket<'port> {
76    port: &'port CompletionPort,
77    socket: OwnedSocket,
78    key: usize,
79    /// What [`AssociatedSocket::set_notification_modes`] has established.
80    ///
81    /// Read at every submission, because it decides whether a synchronous
82    /// success will be followed by a completion packet.
83    modes: NotificationModes,
84}
85
86impl<'port> AssociatedSocket<'port> {
87    /// Borrow the underlying socket.
88    #[must_use]
89    pub fn socket(&self) -> BorrowedSocket<'_> {
90        self.socket.as_socket()
91    }
92
93    /// The completion key packets from this socket are tagged with.
94    #[must_use]
95    pub fn key(&self) -> usize {
96        self.key
97    }
98
99    /// The completion port this socket is associated with.
100    #[must_use]
101    pub fn port(&self) -> &'port CompletionPort {
102        self.port
103    }
104
105    /// The completion-notification modes established on this socket.
106    #[must_use]
107    pub fn notification_modes(&self) -> NotificationModes {
108        self.modes
109    }
110
111    fn raw_socket(&self) -> SOCKET {
112        self.socket.as_raw_socket() as usize
113    }
114
115    /// Set this socket's completion-notification modes, after checking that its
116    /// provider actually supports them.
117    ///
118    /// The handle side declares its modes *before* association, on
119    /// [`UnassociatedEndpoint::set_notification_modes`](crate::UnassociatedEndpoint::set_notification_modes),
120    /// because there the mode is part of an endpoint's provenance. A socket has
121    /// no unassociated stage to hang that on, so it declares here instead.
122    /// Setting after association is still safe: the flag only takes effect at
123    /// I/O time, and `recv`/`send` take `&self`, so a caller sets the mode once
124    /// against `&mut self` and then submits freely.
125    ///
126    /// Passing every field `false` is a no-op call, not a reset. **A mode cannot
127    /// be removed once set** -- a Win32 property of the handle, not a limitation
128    /// of this wrapper -- so a second call can only ever add modes.
129    ///
130    /// # The capability probe
131    ///
132    /// Win32 restricts [`NotificationModes::skip_completion_port_on_success`] on
133    /// a socket to Layered Service Providers that return IFS handles, and a
134    /// socket wrongly put in that mode reports [`Started::Pending`] for an
135    /// operation whose packet was suppressed -- leaving it outstanding forever
136    /// and wedging [`CompletionPort::run_down`]. So this asks first, reading
137    /// *this* socket's own `WSAPROTOCOL_INFOW` via `SO_PROTOCOL_INFOW` and
138    /// requiring `XP1_IFS_HANDLES`. That is narrower and more accurate than the
139    /// `WSAEnumProtocols` sweep the flag's documentation suggests: it asks about
140    /// the provider that actually created this socket, not about every LSP
141    /// installed on the machine.
142    ///
143    /// `skip_set_event_on_handle` carries no such restriction and is not probed.
144    ///
145    /// # Errors
146    ///
147    /// Returns [`io::ErrorKind::Unsupported`] if skip-on-success was requested
148    /// and this socket's provider does not return IFS handles, or any error from
149    /// `getsockopt` or `SetFileCompletionNotificationModes`.
150    pub fn set_notification_modes(&mut self, modes: NotificationModes) -> io::Result<()> {
151        if modes.skip_completion_port_on_success {
152            require_ifs_handles(self.provider_service_flags()?)?;
153        }
154
155        let mut flags = 0_u8;
156        if modes.skip_completion_port_on_success {
157            flags |= notification_flags::SKIP_COMPLETION_PORT_ON_SUCCESS;
158        }
159        if modes.skip_set_event_on_handle {
160            flags |= notification_flags::SKIP_SET_EVENT_ON_HANDLE;
161        }
162        // SAFETY: a live socket this endpoint owns -- a socket handle is a
163        // kernel handle, which is why this file-named call accepts one -- and a
164        // flags byte built only from the two documented bits. The call sets a
165        // handle attribute and starts no I/O.
166        let ok = unsafe {
167            windows_sys::Win32::Storage::FileSystem::SetFileCompletionNotificationModes(
168                self.raw_socket() as HANDLE,
169                flags,
170            )
171        };
172        if ok == 0 {
173            return Err(io::Error::last_os_error());
174        }
175        // Accumulated, never replaced: Win32 cannot clear a mode, so what this
176        // socket records has to be the union of everything ever set on it.
177        self.modes.skip_completion_port_on_success |= modes.skip_completion_port_on_success;
178        self.modes.skip_set_event_on_handle |= modes.skip_set_event_on_handle;
179        Ok(())
180    }
181
182    /// The `dwServiceFlags1` word of the provider that created this socket.
183    fn provider_service_flags(&self) -> io::Result<u32> {
184        let mut info = std::mem::MaybeUninit::<WSAPROTOCOL_INFOW>::uninit();
185        let mut len = i32::try_from(size_of::<WSAPROTOCOL_INFOW>())
186            .expect("WSAPROTOCOL_INFOW is far smaller than i32::MAX");
187        // SAFETY: a live socket, a documented option pair, and an output buffer
188        // exactly `len` bytes long that Winsock fills before returning success.
189        let ret = unsafe {
190            getsockopt(
191                self.raw_socket(),
192                SOL_SOCKET,
193                SO_PROTOCOL_INFOW,
194                info.as_mut_ptr().cast(),
195                &raw mut len,
196            )
197        };
198        if ret == SOCKET_ERROR {
199            return Err(io::Error::last_os_error());
200        }
201        // SAFETY: `getsockopt` reported success, so it wrote the whole struct.
202        Ok(unsafe { info.assume_init() }.dwServiceFlags1)
203    }
204
205    /// Submit an overlapped receive into `buffer`.
206    ///
207    /// The buffer is any owned [`IoBufMut`] -- handed over for the operation's
208    /// life and returned when it completes, with nothing copied and nothing
209    /// allocated here.
210    ///
211    /// Returns [`Started::Pending`] with a [`SocketIo`] token that recovers the
212    /// buffer and byte count from the operation's completion, or -- only on a
213    /// socket in a skip-on-success completion mode, where a synchronous success
214    /// queues no packet -- [`Started::Completed`] with the buffer already in
215    /// hand.
216    ///
217    /// # Errors
218    ///
219    /// Returns [`io::ErrorKind::InvalidInput`] if the buffer is longer than
220    /// `u32::MAX`, which `WSABUF`'s byte count cannot express, or any immediate
221    /// failure from issuing the receive.
222    #[track_caller]
223    pub fn recv<B: IoBufMut>(&self, buffer: B) -> io::Result<Started<SocketIo<B>, B>> {
224        let socket = self.raw_socket();
225        let skip = self.modes.skip_completion_port_on_success;
226        let operation = Operation::new(recv_payload(buffer)?);
227        // SAFETY: issues exactly one WSARecv into the payload's buffer via its
228        // WSABUF and flags word, both reached through the pinned OVERLAPPED; they
229        // and the byte-count cell live until the completion is claimed.
230        let submitted = unsafe {
231            self.port.submit_with(operation, |overlapped| {
232                let payload = payload_ptr_from_overlapped::<SocketPayload<B>>(overlapped);
233                let bytes = sync_bytes_ptr_from_overlapped(overlapped);
234                let ret = WSARecv(
235                    socket,
236                    std::ptr::addr_of!((*payload).wsabuf),
237                    1,
238                    bytes,
239                    std::ptr::addr_of_mut!((*payload).flags),
240                    overlapped,
241                    None,
242                );
243                classify_socket(ret, skip, bytes)
244            })
245        };
246        finish_socket(submitted)
247    }
248
249    /// Submit an overlapped send of `buffer`.
250    ///
251    /// The buffer is any owned [`IoBuf`] -- including a shared `Arc<[u8]>` or a
252    /// `&'static [u8]` -- handed over for the operation's life and returned when
253    /// it completes. Nothing is copied.
254    ///
255    /// Returns [`Started::Pending`] with a [`SocketIo`] token, or
256    /// [`Started::Completed`] with the buffer already in hand when the socket is
257    /// in a skip-on-success completion mode and the send completed
258    /// synchronously.
259    ///
260    /// # Errors
261    ///
262    /// Returns [`io::ErrorKind::InvalidInput`] if the buffer is longer than
263    /// `u32::MAX`, which `WSABUF`'s byte count cannot express, or any immediate
264    /// failure from issuing the send.
265    #[track_caller]
266    pub fn send<B: IoBuf>(&self, buffer: B) -> io::Result<Started<SocketIo<B>, B>> {
267        let socket = self.raw_socket();
268        let skip = self.modes.skip_completion_port_on_success;
269        let operation = Operation::new(send_payload(buffer)?);
270        // SAFETY: issues exactly one WSASend from the payload's buffer via its
271        // WSABUF, reached through the pinned OVERLAPPED; it and the byte-count
272        // cell live until the completion is claimed.
273        let submitted = unsafe {
274            self.port.submit_with(operation, |overlapped| {
275                let payload = payload_ptr_from_overlapped::<SocketPayload<B>>(overlapped);
276                let bytes = sync_bytes_ptr_from_overlapped(overlapped);
277                let ret = WSASend(
278                    socket,
279                    std::ptr::addr_of!((*payload).wsabuf),
280                    1,
281                    bytes,
282                    0,
283                    overlapped,
284                    None,
285                );
286                classify_socket(ret, skip, bytes)
287            })
288        };
289        finish_socket(submitted)
290    }
291
292    /// Request cancellation of a single outstanding operation on this socket.
293    ///
294    /// The identity is validated against the port's live operations, and the
295    /// native cancellation happens under the same guard, so an identity retained
296    /// past its operation's completion cannot reach a later operation that was
297    /// given the same storage address.
298    ///
299    /// # Errors
300    ///
301    /// Returns [`io::ErrorKind::NotFound`] if `id` no longer names a live
302    /// operation, or any error from `CancelIoEx`.
303    pub fn cancel(&self, id: OperationId) -> io::Result<()> {
304        // Socket cancellation goes through the same registry as file
305        // cancellation; routing around it would leave the identity guarantee
306        // holding for one endpoint kind and not the other.
307        self.port.live_operations().cancel_if_live(id, || {
308            // SAFETY: cancelling by a valid socket handle and an OVERLAPPED
309            // identity the registry has confirmed still names a live operation,
310            // and which cannot be reissued while the guard is held.
311            let ok = unsafe { CancelIoEx(self.raw_socket() as HANDLE, id.as_ptr()) };
312            if ok == 0 {
313                return Err(io::Error::last_os_error());
314            }
315            Ok(())
316        })
317    }
318
319    /// Request cancellation of every outstanding operation on this socket.
320    ///
321    /// # Errors
322    ///
323    /// Returns any error from `CancelIoEx`.
324    pub fn cancel_all(&self) -> io::Result<()> {
325        // SAFETY: a null OVERLAPPED cancels all operations on the socket handle.
326        let ok = unsafe { CancelIoEx(self.raw_socket() as HANDLE, std::ptr::null()) };
327        if ok == 0 {
328            return Err(io::Error::last_os_error());
329        }
330        Ok(())
331    }
332}
333
334/// The pinned payload for an in-flight socket operation: the buffer, the
335/// `WSABUF` pointing into it, and the receive `flags` word.
336struct SocketPayload<B> {
337    buffer: B,
338    wsabuf: WSABUF,
339    flags: u32,
340}
341
342// SAFETY: `wsabuf.buf` points into `buffer`, which this payload owns; moving the
343// payload moves the whole self-referential unit -- sound because `IoBuf` promises
344// the buffer's address does not move with it -- and it exposes no aliasing
345// access, so it is `Send` whenever the buffer is.
346unsafe impl<B: Send> Send for SocketPayload<B> {}
347
348fn recv_payload<B: IoBufMut>(mut buffer: B) -> io::Result<SocketPayload<B>> {
349    let wsalen = checked_len(buffer.bytes_len(), "receive buffer")?;
350    let wsabuf = WSABUF {
351        len: wsalen,
352        buf: buffer.stable_mut_ptr(),
353    };
354    Ok(SocketPayload {
355        buffer,
356        wsabuf,
357        flags: 0,
358    })
359}
360
361fn send_payload<B: IoBuf>(buffer: B) -> io::Result<SocketPayload<B>> {
362    let wsabuf = WSABUF {
363        len: checked_len(buffer.bytes_len(), "send buffer")?,
364        // `WSABUF` is one type for both directions, so its `buf` is `*mut` even
365        // for a send. `WSASend` only reads through it, which is what makes this
366        // sound for a shared buffer whose pointer carries no write provenance.
367        buf: buffer.stable_ptr().cast_mut(),
368    };
369    Ok(SocketPayload {
370        buffer,
371        wsabuf,
372        flags: 0,
373    })
374}
375
376/// Decide whether a provider's `dwServiceFlags1` permits skip-on-success.
377///
378/// Split from the `getsockopt` that reads the word so the refusal can be tested
379/// directly: every base Winsock provider on a stock Windows returns IFS handles,
380/// so the failing arm is otherwise unreachable without installing a Layered
381/// Service Provider.
382///
383/// # Errors
384///
385/// Returns [`io::ErrorKind::Unsupported`] -- deliberately not a Win32 error,
386/// because nothing failed: the question was asked and answered.
387fn require_ifs_handles(service_flags1: u32) -> io::Result<()> {
388    if service_flags1 & XP1_IFS_HANDLES == 0 {
389        return Err(io::Error::new(
390            io::ErrorKind::Unsupported,
391            "this socket's provider does not return IFS handles, so Win32 does not support \
392             FILE_SKIP_COMPLETION_PORT_ON_SUCCESS on it",
393        ));
394    }
395    Ok(())
396}
397
398/// Map a Winsock call's return value into the submission contract.
399///
400/// [`Issued`] records whether a **completion packet will arrive**, not whether
401/// the call finished synchronously. For an IOCP-bound overlapped socket those
402/// are different facts: a packet is queued for every completed request,
403/// *including* one that succeeded immediately without `WSA_IO_PENDING`.
404///
405/// The single exception is `skip_on_success`, which is why this needs to know
406/// it: on a socket put into `FILE_SKIP_COMPLETION_PORT_ON_SUCCESS` mode by
407/// [`AssociatedSocket::set_notification_modes`] no packet is queued for an
408/// immediate success, so that -- and only that -- is an [`Issued::Completed`].
409/// Getting this backwards in either direction is a bug with teeth: claiming
410/// `Completed` when a packet is coming frees the operation under a live
411/// `OVERLAPPED`, and claiming `Pending` when none is coming leaves the operation
412/// outstanding forever and wedges [`CompletionPort::run_down`].
413///
414/// # Safety
415///
416/// `sync_bytes` must be the byte-count cell of the operation being submitted,
417/// which is live for the whole call.
418unsafe fn classify_socket(
419    ret: i32,
420    skip_on_success: bool,
421    sync_bytes: *mut u32,
422) -> io::Result<Issued> {
423    if ret == 0 {
424        if skip_on_success {
425            // SAFETY: the call reported immediate success, so Winsock has
426            // already written the count and will not write it again.
427            let bytes_transferred = unsafe { *sync_bytes };
428            return Ok(Issued::Completed { bytes_transferred });
429        }
430        return Ok(Issued::Pending);
431    }
432    let error = io::Error::last_os_error();
433    if error.raw_os_error() == Some(WSA_IO_PENDING) {
434        Ok(Issued::Pending)
435    } else {
436        Err(error)
437    }
438}
439
440/// Turn a socket submission outcome into the adapter's two-state outcome.
441fn finish_socket<B: IoBuf>(
442    submitted: Submitted<SocketPayload<B>>,
443) -> io::Result<Started<SocketIo<B>, B>> {
444    match submitted {
445        Submitted::Pending(id) => Ok(Started::Pending(SocketIo {
446            id,
447            buffer: std::marker::PhantomData,
448        })),
449        Submitted::Completed {
450            operation,
451            bytes_transferred,
452        } => Ok(Started::Completed {
453            payload: operation.into_payload().buffer,
454            bytes_transferred: bytes_transferred as usize,
455        }),
456        Submitted::Failed { error, .. } => Err(error),
457    }
458}
459
460/// Convert a buffer length to the `u32` byte count `WSABUF` carries.
461///
462/// Rejects rather than caps, for the same reason as the file and device
463/// helpers: capping would transfer a prefix of the caller's buffer and then
464/// report success for an operation that did something other than what was asked.
465fn checked_len(len: usize, which: &str) -> io::Result<u32> {
466    u32::try_from(len).map_err(|_| {
467        io::Error::new(
468            io::ErrorKind::InvalidInput,
469            format!("a {which} is limited to u32::MAX bytes; {len} does not fit"),
470        )
471    })
472}
473
474/// A pending socket operation submitted through [`AssociatedSocket::recv`] or
475/// [`AssociatedSocket::send`].
476///
477/// The token carries the operation's identity and remembers the buffer type it
478/// was submitted with, so [`SocketIo::claim`] hands back the caller's own buffer
479/// -- the same value, not a copy -- once the matching completion is dequeued.
480#[derive(Debug)]
481pub struct SocketIo<B> {
482    id: OperationId,
483    /// The buffer itself is in the pinned operation, not here; this only keeps
484    /// the token's type tied to it so `claim` cannot be handed the wrong one.
485    buffer: std::marker::PhantomData<fn() -> B>,
486}
487
488impl<B: IoBuf> SocketIo<B> {
489    /// The identity of the in-flight operation, for cancellation or matching.
490    #[must_use]
491    pub fn id(&self) -> OperationId {
492        self.id
493    }
494
495    /// Claim this operation's result from `completion`.
496    ///
497    /// On a match returns `Ok((buffer, result))`: `buffer` is the one the caller
498    /// handed over -- the bytes received (valid up to the byte count), or the
499    /// data sent -- and `result` is the byte count or the operation's error.
500    /// Returns `Err(self)` when `completion` belongs to a different operation.
501    pub fn claim(self, completion: &Completion) -> Result<(B, io::Result<usize>), Self> {
502        if completion.id() != Some(self.id) {
503            return Err(self);
504        }
505        // SAFETY: the full identity -- address *and* generation -- matches, which
506        // an address alone would not: a recycled address can belong to a later
507        // operation of a different payload type. The match therefore proves this
508        // completion is the Operation<SocketPayload<B>> this token submitted, and
509        // the token's own type parameter names that B; claim it exactly once.
510        let operation = unsafe { completion.claim::<SocketPayload<B>>() };
511        let buffer = operation.into_payload().buffer;
512        let result = match completion.error() {
513            Some(error) => Err(io::Error::from_raw_os_error(
514                error.raw_os_error().unwrap_or_default(),
515            )),
516            None => Ok(completion.bytes_transferred() as usize),
517        };
518        Ok((buffer, result))
519    }
520}
521
522/// A connected overlapped socket that completes operations synchronously, one at
523/// a time, via a Winsock completion event.
524///
525/// This is the socket counterpart of the handle blocking backend. It cannot use
526/// `GetOverlappedResult` on the socket handle, so each call creates a
527/// `WSACreateEvent`, issues the operation with that event in `OVERLAPPED.hEvent`,
528/// and blocks on `WSAGetOverlappedResult`.
529#[derive(Debug)]
530pub struct BlockingSocket {
531    socket: OwnedSocket,
532}
533
534impl BlockingSocket {
535    /// Take ownership of a connected overlapped socket for synchronous
536    /// completion.
537    #[must_use]
538    pub fn new(socket: OwnedSocket) -> Self {
539        Self { socket }
540    }
541
542    /// Borrow the underlying socket.
543    #[must_use]
544    pub fn socket(&self) -> BorrowedSocket<'_> {
545        self.socket.as_socket()
546    }
547
548    fn raw_socket(&self) -> SOCKET {
549        self.socket.as_raw_socket() as usize
550    }
551
552    /// Receive into `buffer`, blocking until the receive completes, and return
553    /// the number of bytes received.
554    ///
555    /// Takes a plain `&mut [u8]` and allocates nothing, matching
556    /// [`BlockingSocket::send`]: this call does not return until the operation
557    /// is over, so an ordinary borrow provably covers it.
558    ///
559    /// # Errors
560    ///
561    /// Returns [`io::ErrorKind::InvalidInput`] if `buffer` is longer than
562    /// `u32::MAX`, which `WSABUF`'s byte count cannot express, or any error from
563    /// issuing or completing the receive.
564    pub fn recv(&self, buffer: &mut [u8]) -> io::Result<usize> {
565        let wsalen = checked_len(buffer.len(), "receive buffer")?;
566        let wsabuf = WSABUF {
567            len: wsalen,
568            buf: buffer.as_mut_ptr(),
569        };
570        // SAFETY: issues exactly one WSARecv into `buffer` via `wsabuf`, both of
571        // which stay valid for the whole blocking call.
572        unsafe {
573            self.run(|socket, overlapped| {
574                let mut flags = 0_u32;
575                WSARecv(
576                    socket,
577                    &wsabuf,
578                    1,
579                    std::ptr::null_mut(),
580                    &mut flags,
581                    overlapped,
582                    None,
583                )
584            })
585        }
586    }
587
588    /// Send `data`, blocking until the send completes, and return the bytes sent.
589    ///
590    /// # Errors
591    ///
592    /// Returns [`io::ErrorKind::InvalidInput`] if `data` is longer than
593    /// `u32::MAX`, which `WSABUF`'s byte count cannot express, or any error from
594    /// issuing or completing the send.
595    pub fn send(&self, data: &[u8]) -> io::Result<usize> {
596        let wsabuf = WSABUF {
597            len: checked_len(data.len(), "send buffer")?,
598            buf: data.as_ptr().cast_mut(),
599        };
600        // SAFETY: issues exactly one WSASend from `data` via `wsabuf`, both of
601        // which stay valid for the whole blocking call; WSASend does not write
602        // through the buffer pointer.
603        unsafe {
604            self.run(|socket, overlapped| {
605                WSASend(
606                    socket,
607                    &wsabuf,
608                    1,
609                    std::ptr::null_mut(),
610                    0,
611                    overlapped,
612                    None,
613                )
614            })
615        }
616    }
617
618    /// Issue one overlapped socket operation with a completion event and block on
619    /// `WSAGetOverlappedResult` until it finishes, returning the bytes transferred.
620    ///
621    /// # Safety
622    ///
623    /// `issue` must start exactly one overlapped operation using the provided
624    /// socket and `OVERLAPPED`, with any buffers valid for the whole call.
625    unsafe fn run<F>(&self, issue: F) -> io::Result<usize>
626    where
627        F: FnOnce(SOCKET, *mut OVERLAPPED) -> i32,
628    {
629        let socket = self.raw_socket();
630        // SAFETY: creates a manual-reset Winsock event; the null handle
631        // `WSA_INVALID_EVENT` signals failure.
632        let event: WSAEVENT = unsafe { WSACreateEvent() };
633        if event == WSA_INVALID_EVENT {
634            return Err(io::Error::last_os_error());
635        }
636
637        // SAFETY: `OVERLAPPED` is plain data; a zeroed value with the event in
638        // `hEvent` is the documented way to wait for a single operation.
639        let mut overlapped: OVERLAPPED = unsafe { std::mem::zeroed() };
640        overlapped.hEvent = event as HANDLE;
641
642        let ret = issue(socket, &mut overlapped);
643        if ret != 0 {
644            let error = io::Error::last_os_error();
645            if error.raw_os_error() != Some(WSA_IO_PENDING) {
646                // SAFETY: `event` was created above and is closed exactly once.
647                unsafe { WSACloseEvent(event) };
648                return Err(error);
649            }
650        }
651
652        let mut transferred = 0_u32;
653        let mut flags = 0_u32;
654        // SAFETY: waits on `overlapped`'s event for this one operation to finish.
655        let ok = unsafe {
656            WSAGetOverlappedResult(
657                socket,
658                &overlapped,
659                &mut transferred,
660                i32::from(true),
661                &mut flags,
662            )
663        };
664        let result = if ok == 0 {
665            Err(io::Error::last_os_error())
666        } else {
667            Ok(transferred as usize)
668        };
669        // SAFETY: `event` was created above and is closed exactly once here.
670        unsafe { WSACloseEvent(event) };
671        result
672    }
673}
674
675#[cfg(test)]
676mod tests;