Skip to main content

gns/
lib.rs

1//! # Rust wrapper for Valve GameNetworkingSockets
2//!
3//! This crate wraps the low-level GameNetworkingSockets library and gives you
4//! two things:
5//!
6//! - **Type safety.** The socket type records its own state, so the compiler
7//!   rejects any operation that the current state does not allow. Every public
8//!   operation is safe to call.
9//! - **A high-level API.** You never write FFI code. The API is plain,
10//!   idiomatic Rust.
11//!
12//! # Example
13//!
14//! ```
15//! use gns::{GnsGlobal, GnsSocket, IsCreated};
16//! use std::net::Ipv6Addr;
17//! use std::time::Duration;
18//!
19//! // Do not use `unwrap` in production. This example uses it to keep the
20//! // interesting calls easy to read.
21//!
22//! // Initialize the global networking state. A process has exactly one.
23//! let gns_global = GnsGlobal::get().unwrap();
24//!
25//! // Create a socket. The type parameter records the socket state, and
26//! // `GnsSocket::new` is only available in the initial `IsCreated` state.
27//! let gns_socket = GnsSocket::<IsCreated>::new(gns_global);
28//!
29//! // Choose your own port.
30//! let port = 9001;
31//!
32//! // `connect` moves the socket from `IsCreated` to `IsClient`, which gives
33//! // you the client operations.
34//! let client = gns_socket.connect(Ipv6Addr::LOCALHOST.into(), port).unwrap();
35//!
36//! // A connected socket needs three calls in your main loop:
37//! //
38//! // 1. Poll for new messages.
39//! // 2. Poll for connection status changes.
40//! // 3. Poll for the low-level callbacks that the underlying library needs.
41//! //
42//! // Clients and servers use the same three calls. Only the scope differs. On
43//! // a client they cover the single connection. On a server they cover every
44//! // connected client.
45//!
46//! // Run the low-level callbacks.
47//! gns_global.poll_callbacks();
48//!
49//! // Receive at most 100 messages and print each payload.
50//! for message in client.receive_messages::<100>().expect("failed to recv").into_iter() {
51//!   println!("{}", core::str::from_utf8(message.payload()).unwrap());
52//! }
53//!
54//! // This example ignores events. A real program reads them to react when the
55//! // connection opens or closes.
56//! for _event in client.receive_events() {
57//! }
58//!
59//! // Wait before the next iteration.
60//! std::thread::sleep(Duration::from_millis(10))
61//! ```
62//!
63//! # How events reach a socket
64//!
65//! Each [`GnsSocket`] registers a weak reference to its event queue with
66//! [`GnsGlobal`]. When GameNetworkingSockets reports a connection-state change,
67//! the callback uses that registry to find the socket the event belongs to.
68//! Dropping the socket removes its entry.
69
70use crossbeam_queue::SegQueue;
71pub use gns_sys as sys;
72use std::sync::atomic::{AtomicI64, Ordering};
73use std::{
74    collections::HashMap,
75    ffi::{c_void, CStr, CString},
76    marker::PhantomData,
77    mem::MaybeUninit,
78    net::{IpAddr, Ipv4Addr, Ipv6Addr},
79    sync::{Arc, Mutex, OnceLock, RwLock, Weak},
80    time::Duration,
81};
82use sys::*;
83
84#[inline]
85fn get_interface() -> *mut ISteamNetworkingSockets {
86    unsafe { SteamAPI_SteamNetworkingSockets_v009() }
87}
88
89#[inline]
90fn get_utils() -> *mut ISteamNetworkingUtils {
91    unsafe { SteamAPI_SteamNetworkingUtils_v003() }
92}
93
94/// A network message number. This alias exists to make signatures readable.
95pub type GnsMessageNumber = u64;
96
97/// An error returned by the wrapper.
98///
99/// Most variants wrap the [`EResult`] that the underlying API returned. The
100/// rest cover setup paths that report failure without an `EResult`.
101#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
102pub enum GnsError {
103    #[error("GameNetworkingSockets_Init failed: {0}")]
104    Init(String),
105    #[error("listen failed: invalid handle")]
106    Listen,
107    #[error("connect failed: invalid handle")]
108    Connect,
109    #[error("receive failed: invalid connection or poll group handle")]
110    Receive,
111    #[error("accept failed: could not set connection poll group")]
112    Accept,
113    #[error("close failed: invalid connection handle")]
114    Close,
115    #[error("steam api: {0:?}")]
116    Api(EResult),
117    #[error("config: {0}")]
118    Config(&'static str),
119}
120
121pub type GnsResult<T> = Result<T, GnsError>;
122
123/// Converts an `EResult` returned by an FFI call into a [`GnsResult`].
124#[inline]
125fn check(e: EResult) -> GnsResult<()> {
126    match e {
127        EResult::k_EResultOK => Ok(()),
128        e => Err(GnsError::Api(e)),
129    }
130}
131
132/// Owns the initialization and teardown of GameNetworkingSockets and its
133/// singletons.
134///
135/// Call [`GnsGlobal::get()`] to obtain the instance. The first call initializes
136/// GameNetworkingSockets, and later calls return the same instance.
137pub struct GnsGlobal {
138    utils: GnsUtils,
139    next_queue_id: AtomicI64,
140    /// Maps each socket to its event queue.
141    ///
142    /// Reads dominate: every connection-state callback from the
143    /// GameNetworkingSockets service thread performs one lookup. Writes happen
144    /// only when a socket is created or dropped, or in the rare case where a
145    /// callback arrives for a socket that was dropped moments earlier. An
146    /// `RwLock` lets those reads run concurrently.
147    event_queues: RwLock<HashMap<i64, Weak<SegQueue<GnsConnectionEvent>>>>,
148}
149
150static GNS_GLOBAL: OnceLock<GnsGlobal> = OnceLock::new();
151
152impl Drop for GnsGlobal {
153    #[inline]
154    fn drop(&mut self) {
155        // Stop the service thread and tear down the internal state.
156        //
157        // GameNetworkingSockets does not support an init, kill, init cycle on
158        // every version, so this runs only when the singleton itself is
159        // dropped.
160        unsafe { GameNetworkingSockets_Kill() }
161    }
162}
163
164impl GnsGlobal {
165    /// Returns a reference to the [`GnsGlobal`] instance.
166    ///
167    /// The first call initializes GameNetworkingSockets through
168    /// [`sys::GameNetworkingSockets_Init`]. Later calls return the instance
169    /// that call created.
170    ///
171    /// # Errors
172    /// Returns [`GnsError::Init`] with the message that GameNetworkingSockets
173    /// produced if initialization fails.
174    pub fn get() -> GnsResult<&'static Self> {
175        // Fast path: no lock
176        if let Some(g) = GNS_GLOBAL.get() {
177            return Ok(g);
178        }
179        // use get_or_try_init once stabilized: https://github.com/rust-lang/rust/issues/109737
180        static INIT_LOCK: Mutex<()> = Mutex::new(());
181        let _guard = INIT_LOCK.lock().unwrap();
182        if let Some(g) = GNS_GLOBAL.get() {
183            return Ok(g);
184        }
185        unsafe {
186            let mut error: SteamDatagramErrMsg = MaybeUninit::zeroed().assume_init();
187            if !GameNetworkingSockets_Init(core::ptr::null(), &mut error) {
188                return Err(GnsError::Init(
189                    CStr::from_ptr(error.as_ptr())
190                        .to_str()
191                        .unwrap_or("")
192                        .to_owned(),
193                ));
194            }
195        }
196        let _ = GNS_GLOBAL.set(GnsGlobal {
197            utils: GnsUtils(()),
198            next_queue_id: AtomicI64::new(0),
199            event_queues: RwLock::new(HashMap::new()),
200        });
201        Ok(GNS_GLOBAL.get().expect("impossible; qed;"))
202    }
203
204    #[inline]
205    pub fn poll_callbacks(&self) {
206        unsafe {
207            SteamAPI_ISteamNetworkingSockets_RunCallbacks(get_interface());
208        }
209    }
210
211    #[inline]
212    pub fn utils(&self) -> &GnsUtils {
213        &self.utils
214    }
215
216    #[inline]
217    pub fn queue_count(&self) -> usize {
218        self.event_queues.read().unwrap().len()
219    }
220
221    #[inline]
222    fn create_queue(&self) -> (i64, Arc<SegQueue<GnsConnectionEvent>>) {
223        let queue = Arc::new(SegQueue::new());
224        let queue_id = self.next_queue_id.fetch_add(1, Ordering::SeqCst);
225        self.event_queues
226            .write()
227            .unwrap()
228            .insert(queue_id, Arc::downgrade(&queue));
229        (queue_id, queue)
230    }
231}
232
233/// An opaque wrapper around [`sys::HSteamListenSocket`].
234#[repr(transparent)]
235pub(crate) struct GnsListenSocket(HSteamListenSocket);
236
237/// An opaque wrapper around [`sys::HSteamNetPollGroup`].
238#[repr(transparent)]
239pub(crate) struct GnsPollGroup(HSteamNetPollGroup);
240
241/// The initial state of a [`GnsSocket`].
242///
243/// A socket in this state is neither a client nor a server yet, so it holds no
244/// data.
245pub struct IsCreated;
246
247mod private {
248    pub trait Sealed {}
249    impl Sealed for super::IsServer {}
250    impl Sealed for super::IsClient {}
251}
252
253/// The operations that every ready [`GnsSocket`] supports.
254///
255/// A ready socket is either a client or a server. Both can read connection
256/// events and receive messages.
257pub trait IsReady: private::Sealed {
258    /// Returns the connection event queue. The queue is thread-safe.
259    fn queue(&self) -> &SegQueue<GnsConnectionEvent>;
260    /// Receives up to `slots.len()` messages into `slots`.
261    ///
262    /// Returns the number of slots that GameNetworkingSockets filled, or
263    /// [`GnsError::Receive`] if the underlying handle is invalid.
264    fn receive(&self, slots: &mut [MaybeUninit<*mut ISteamNetworkingMessage>]) -> GnsResult<usize>;
265}
266
267/// The state of a [`GnsSocket`] that acts as a server, normally reached
268/// through [`GnsSocket::listen`].
269///
270/// In this state the socket holds what it needs to accept connections and poll
271/// them for messages.
272pub struct IsServer {
273    queue: Arc<SegQueue<GnsConnectionEvent>>,
274    queue_id: i64,
275    global: &'static GnsGlobal,
276    listen_socket: GnsListenSocket,
277    poll_group: GnsPollGroup,
278}
279
280impl Drop for IsServer {
281    #[inline]
282    fn drop(&mut self) {
283        unsafe {
284            SteamAPI_ISteamNetworkingSockets_CloseListenSocket(
285                get_interface(),
286                self.listen_socket.0,
287            );
288            SteamAPI_ISteamNetworkingSockets_DestroyPollGroup(get_interface(), self.poll_group.0);
289        }
290        self.global
291            .event_queues
292            .write()
293            .unwrap()
294            .remove(&self.queue_id);
295    }
296}
297
298impl IsReady for IsServer {
299    #[inline]
300    fn queue(&self) -> &SegQueue<GnsConnectionEvent> {
301        &self.queue
302    }
303
304    fn receive(&self, slots: &mut [MaybeUninit<*mut ISteamNetworkingMessage>]) -> GnsResult<usize> {
305        let result = unsafe {
306            SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup(
307                get_interface(),
308                self.poll_group.0,
309                slots.as_mut_ptr() as _,
310                slots.len() as _,
311            ) as _
312        };
313        if result == usize::MAX {
314            Err(GnsError::Receive)
315        } else {
316            Ok(result)
317        }
318    }
319}
320
321/// The state of a [`GnsSocket`] that acts as a client, normally reached
322/// through [`GnsSocket::connect`].
323///
324/// In this state the socket holds what it needs to send and receive messages.
325pub struct IsClient {
326    queue: Arc<SegQueue<GnsConnectionEvent>>,
327    queue_id: i64,
328    global: &'static GnsGlobal,
329    connection: GnsConnection,
330}
331
332impl Drop for IsClient {
333    fn drop(&mut self) {
334        unsafe {
335            SteamAPI_ISteamNetworkingSockets_CloseConnection(
336                get_interface(),
337                self.connection.0,
338                0,
339                core::ptr::null(),
340                false,
341            );
342        }
343        self.global
344            .event_queues
345            .write()
346            .unwrap()
347            .remove(&self.queue_id);
348    }
349}
350
351impl IsReady for IsClient {
352    #[inline]
353    fn queue(&self) -> &SegQueue<GnsConnectionEvent> {
354        &self.queue
355    }
356
357    fn receive(&self, slots: &mut [MaybeUninit<*mut ISteamNetworkingMessage>]) -> GnsResult<usize> {
358        let result = unsafe {
359            SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnConnection(
360                get_interface(),
361                self.connection.0,
362                slots.as_mut_ptr() as _,
363                slots.len() as _,
364            ) as _
365        };
366        if result == usize::MAX {
367            Err(GnsError::Receive)
368        } else {
369            Ok(result)
370        }
371    }
372}
373
374pub struct ToReceive(());
375
376pub struct ToSend(());
377
378/// A single receive slot.
379///
380/// Each slot is an uninitialized cell that GameNetworkingSockets fills with one
381/// `*mut ISteamNetworkingMessage`. Build a buffer of slots, for example
382/// `[const { MessageSlot::uninit() }; 128]`, and pass it to
383/// [`GnsSocket::receive_messages_into`].
384pub type MessageSlot = MaybeUninit<*mut ISteamNetworkingMessage>;
385
386/// Rebuilds the owned message stored in `slot`.
387///
388/// # Safety
389/// GameNetworkingSockets must have initialized `slot`, meaning the slot lies
390/// within the prefix length that `receive` reported. The slot must not have
391/// been taken already, otherwise the message is released more than once.
392#[inline]
393unsafe fn take_message(slot: &MessageSlot) -> GnsNetworkMessage<ToReceive> {
394    GnsNetworkMessage(unsafe { slot.assume_init() }, PhantomData)
395}
396
397/// Tracks progress through a buffer of receive slots.
398///
399/// The slots in `slots[..len]` are initialized, and `pos` is the next slot to
400/// hand out. This type holds the unsafe take and release logic in one place so
401/// that the owning and borrowing iterators cannot drift apart.
402struct SlotCursor {
403    len: usize,
404    pos: usize,
405}
406
407impl SlotCursor {
408    fn next(&mut self, slots: &[MessageSlot]) -> Option<GnsNetworkMessage<ToReceive>> {
409        if self.pos < self.len {
410            // Safety: GameNetworkingSockets initialized `slots[..len]`, and
411            // `pos` only increases, so each slot is taken at most once.
412            let message = unsafe { take_message(&slots[self.pos]) };
413            self.pos += 1;
414            Some(message)
415        } else {
416            None
417        }
418    }
419
420    #[inline]
421    fn remaining(&self) -> usize {
422        self.len - self.pos
423    }
424
425    /// Releases every slot that was not handed out. Safe to call more than
426    /// once.
427    fn drain_unconsumed(&mut self, slots: &[MessageSlot]) {
428        for slot in &slots[self.pos..self.len] {
429            // Safety: same invariant as `next`. These slots are initialized
430            // and were never handed out, so each is released exactly once.
431            drop(unsafe { take_message(slot) });
432        }
433        self.pos = self.len;
434    }
435}
436
437/// An iterator over the messages from one [`GnsSocket::receive_messages`]
438/// call.
439///
440/// The iterator owns its `K`-slot pointer buffer inline, so it performs no heap
441/// allocation. It yields each [`GnsNetworkMessage<ToReceive>`] by value, and
442/// releases any message you did not consume when it is dropped.
443///
444/// See [`GnsSocket::receive_messages_into`] for a variant that borrows a buffer
445/// you own, which also avoids moving the inline array.
446pub struct ReceivedMessages<const K: usize> {
447    slots: [MessageSlot; K],
448    cursor: SlotCursor,
449}
450
451impl<const K: usize> Iterator for ReceivedMessages<K> {
452    type Item = GnsNetworkMessage<ToReceive>;
453
454    #[inline]
455    fn next(&mut self) -> Option<Self::Item> {
456        self.cursor.next(&self.slots)
457    }
458
459    #[inline]
460    fn size_hint(&self) -> (usize, Option<usize>) {
461        let remaining = self.cursor.remaining();
462        (remaining, Some(remaining))
463    }
464}
465
466impl<const K: usize> ExactSizeIterator for ReceivedMessages<K> {}
467
468impl<const K: usize> core::iter::FusedIterator for ReceivedMessages<K> {}
469
470impl<const K: usize> Drop for ReceivedMessages<K> {
471    #[inline]
472    fn drop(&mut self) {
473        self.cursor.drain_unconsumed(&self.slots);
474    }
475}
476
477/// An iterator returned by [`GnsSocket::receive_messages_into`].
478///
479/// The iterator borrows your buffer for its whole lifetime, so you cannot reuse
480/// the buffer while messages are still outstanding. It yields each
481/// [`GnsNetworkMessage<ToReceive>`] by value.
482///
483/// Nothing is allocated and the pointer buffer never moves. Only the individual
484/// message pointers move. Any message you did not consume is released when the
485/// iterator is dropped.
486pub struct ReceivedMessagesInto<'a> {
487    slots: &'a mut [MessageSlot],
488    cursor: SlotCursor,
489}
490
491impl Iterator for ReceivedMessagesInto<'_> {
492    type Item = GnsNetworkMessage<ToReceive>;
493
494    #[inline]
495    fn next(&mut self) -> Option<Self::Item> {
496        self.cursor.next(self.slots)
497    }
498
499    #[inline]
500    fn size_hint(&self) -> (usize, Option<usize>) {
501        let remaining = self.cursor.remaining();
502        (remaining, Some(remaining))
503    }
504}
505
506impl ExactSizeIterator for ReceivedMessagesInto<'_> {}
507
508impl core::iter::FusedIterator for ReceivedMessagesInto<'_> {}
509
510impl Drop for ReceivedMessagesInto<'_> {
511    #[inline]
512    fn drop(&mut self) {
513        self.cursor.drain_unconsumed(self.slots);
514    }
515}
516
517bitflags::bitflags! {
518    /// A type-safe wrapper over the `k_nSteamNetworkingSend_*` flags.
519    ///
520    /// The bit values match the raw `c_int` constants.
521    #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
522    pub struct SendFlags: i32 {
523        const UNRELIABLE                  = sys::k_nSteamNetworkingSend_Unreliable;
524        const NO_NAGLE                    = sys::k_nSteamNetworkingSend_NoNagle;
525        const NO_DELAY                    = sys::k_nSteamNetworkingSend_NoDelay;
526        const RELIABLE                    = sys::k_nSteamNetworkingSend_Reliable;
527        const USE_CURRENT_THREAD          = sys::k_nSteamNetworkingSend_UseCurrentThread;
528        const AUTO_RESTART_BROKEN_SESSION = sys::k_nSteamNetworkingSend_AutoRestartBrokenSession;
529    }
530}
531
532/// A connection lane.
533///
534/// `priority` is a signed C `int` where a lower value means a higher priority.
535/// `weight` is the relative scheduling weight within one priority class.
536#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
537pub struct GnsLane {
538    pub priority: i32,
539    pub weight: u16,
540}
541
542impl GnsLane {
543    #[inline]
544    pub const fn new(priority: i32, weight: u16) -> Self {
545        Self { priority, weight }
546    }
547}
548
549/// A lane identifier.
550pub type GnsLaneId = u16;
551
552/// The result of one message in a [`GnsSocket::send_messages`] batch.
553///
554/// `Skipped` mirrors how GameNetworkingSockets handles a failed batch. Once a
555/// message fails on a connection, every later message in the same batch that
556/// targets that connection is skipped without being attempted, and its result
557/// is reported as `0`. A skipped message keeps its payload, so the wrapper
558/// returns it to you, the same way it returns a `Failed` message.
559#[must_use = "Failed/Skipped variants own a message that needs inspection or drop"]
560pub enum SendOutcome {
561    Sent(GnsMessageNumber),
562    Failed(EResult, GnsNetworkMessage<ToSend>),
563    Skipped(GnsNetworkMessage<ToSend>),
564}
565
566/// An owned byte buffer for an outbound message.
567///
568/// GameNetworkingSockets reads `m_pData` on its service thread after
569/// `SendMessages` returns, so a message must own its bytes until
570/// GameNetworkingSockets releases it.
571///
572/// [`into_raw`](Self::into_raw) returns a `(pointer, length)` pair that the
573/// wrapper stores unchanged in `m_pData` and `m_cbSize`. When
574/// GameNetworkingSockets releases the message, the wrapper passes those same
575/// values to [`from_raw`](Self::from_raw) to rebuild `Self`, then drops the
576/// result.
577///
578/// This mirrors `Box::into_raw` and `Box::from_raw`, so you can express how the
579/// buffer is freed with an ordinary Rust `Drop` implementation.
580///
581/// # Safety
582/// `from_raw(p, n)` must be sound whenever `(p, n)` came from an earlier
583/// `into_raw` call on the same implementation. In other words, `from_raw` must
584/// undo `into_raw` exactly.
585///
586/// `into_raw` must not run the `Drop` implementation of `Self`, because
587/// ownership passes to GameNetworkingSockets.
588pub unsafe trait Payload: Send + 'static {
589    fn into_raw(self) -> (*mut u8, usize);
590    /// # Safety
591    /// `ptr` and `len` must be the values that an earlier
592    /// [`into_raw`](Self::into_raw) call on this same implementation returned,
593    /// and that ownership must not have been reclaimed already.
594    unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self;
595}
596
597/// The `m_pfnFreeData` callback that the wrapper installs on every
598/// `GnsNetworkMessage<ToSend>`.
599///
600/// It reads `m_pData` and `m_cbSize`, rebuilds `P` with
601/// [`Payload::from_raw`], and drops the result.
602extern "C" fn free_payload<P: Payload>(msg: *mut ISteamNetworkingMessage) {
603    let ptr = unsafe { (*msg).m_pData } as *mut u8;
604    let len = unsafe { (*msg).m_cbSize } as usize;
605    // Safety: `GnsNetworkMessage::<ToSend>::new` wrote `ptr` and `len` from
606    // `P::into_raw`, and GameNetworkingSockets releases each message once.
607    drop(unsafe { P::from_raw(ptr, len) });
608}
609
610unsafe impl Payload for Box<[u8]> {
611    #[inline]
612    fn into_raw(self) -> (*mut u8, usize) {
613        let len = self.len();
614        let raw = Box::into_raw(self) as *mut u8;
615        (raw, len)
616    }
617    #[inline]
618    unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self {
619        let slice = core::ptr::slice_from_raw_parts_mut(ptr, len);
620        unsafe { Box::from_raw(slice) }
621    }
622}
623
624// This goes through `Box<[u8]>`. `into_boxed_slice` shrinks the buffer to fit,
625// which costs one reallocation when the capacity differs from the length, so
626// the pointer and length are enough to rebuild the value.
627unsafe impl Payload for Vec<u8> {
628    #[inline]
629    fn into_raw(self) -> (*mut u8, usize) {
630        <Box<[u8]> as Payload>::into_raw(self.into_boxed_slice())
631    }
632    #[inline]
633    unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self {
634        unsafe { Vec::from_raw_parts(ptr, len, len) }
635    }
636}
637
638unsafe impl Payload for String {
639    #[inline]
640    fn into_raw(self) -> (*mut u8, usize) {
641        <Vec<u8> as Payload>::into_raw(self.into_bytes())
642    }
643    #[inline]
644    unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self {
645        unsafe { String::from_raw_parts(ptr, len, len) }
646    }
647}
648
649unsafe impl Payload for Arc<[u8]> {
650    #[inline]
651    fn into_raw(self) -> (*mut u8, usize) {
652        let len = self.len();
653        let raw = Arc::into_raw(self) as *const u8 as *mut u8;
654        (raw, len)
655    }
656    #[inline]
657    unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self {
658        let slice = core::ptr::slice_from_raw_parts(ptr as *const u8, len);
659        unsafe { Arc::from_raw(slice) }
660    }
661}
662
663unsafe impl Payload for &'static [u8] {
664    #[inline]
665    fn into_raw(self) -> (*mut u8, usize) {
666        (self.as_ptr() as *mut u8, self.len())
667    }
668    #[inline]
669    unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self {
670        unsafe { core::slice::from_raw_parts(ptr as *const u8, len) }
671    }
672}
673
674unsafe impl Payload for &'static str {
675    #[inline]
676    fn into_raw(self) -> (*mut u8, usize) {
677        (self.as_ptr() as *mut u8, self.len())
678    }
679    #[inline]
680    unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self {
681        let bytes = unsafe { core::slice::from_raw_parts(ptr as *const u8, len) };
682        unsafe { core::str::from_utf8_unchecked(bytes) }
683    }
684}
685
686/// A GameNetworkingSockets message, tagged with its direction.
687///
688/// The library produces `ToReceive` messages. You create `ToSend` messages with
689/// [`GnsUtils::allocate_message`], and they own their payload through
690/// [`Payload`]. Both kinds are released when dropped.
691#[repr(transparent)]
692pub struct GnsNetworkMessage<T>(*mut ISteamNetworkingMessage, PhantomData<T>);
693
694impl<T> Drop for GnsNetworkMessage<T> {
695    #[inline]
696    fn drop(&mut self) {
697        if !self.0.is_null() {
698            unsafe {
699                SteamAPI_SteamNetworkingMessage_t_Release(self.0);
700            }
701        }
702    }
703}
704
705impl<T> GnsNetworkMessage<T> {
706    /// Returns the raw `*mut ISteamNetworkingMessage` and forgets the wrapper.
707    ///
708    /// # Safety
709    /// You take over releasing the message, for example by calling
710    /// `SteamAPI_SteamNetworkingMessage_t_Release`. For a `ToSend` message that
711    /// release also runs the `m_pfnFreeData` callback that [`Payload`]
712    /// installed.
713    #[inline]
714    pub unsafe fn into_inner(self) -> *mut ISteamNetworkingMessage {
715        // Hold off the wrapper's destructor. Releasing the message is now
716        // the caller's job. Without this the message would be released here
717        // and the returned pointer would dangle.
718        core::mem::ManuallyDrop::new(self).0
719    }
720
721    #[inline]
722    pub fn payload(&self) -> &[u8] {
723        unsafe {
724            core::slice::from_raw_parts((*self.0).m_pData as *const u8, (*self.0).m_cbSize as _)
725        }
726    }
727
728    #[inline]
729    pub fn message_number(&self) -> u64 {
730        unsafe { (*self.0).m_nMessageNumber as _ }
731    }
732
733    #[inline]
734    pub fn lane(&self) -> GnsLaneId {
735        unsafe { (*self.0).m_idxLane }
736    }
737
738    #[inline]
739    pub fn flags(&self) -> SendFlags {
740        SendFlags::from_bits_retain(unsafe { (*self.0).m_nFlags })
741    }
742
743    #[inline]
744    pub fn user_data(&self) -> u64 {
745        unsafe { (*self.0).m_nUserData as _ }
746    }
747
748    #[inline]
749    pub fn connection(&self) -> GnsConnection {
750        GnsConnection(unsafe { (*self.0).m_conn })
751    }
752
753    #[inline]
754    pub fn connection_user_data(&self) -> u64 {
755        unsafe { (*self.0).m_nConnUserData as _ }
756    }
757}
758
759impl GnsNetworkMessage<ToSend> {
760    #[inline]
761    fn new<P: Payload>(
762        ptr: *mut ISteamNetworkingMessage,
763        conn: GnsConnection,
764        flags: SendFlags,
765        payload: P,
766    ) -> Self {
767        let (data_ptr, len) = payload.into_raw();
768        unsafe {
769            (*ptr).m_pData = data_ptr as *mut c_void;
770            (*ptr).m_cbSize = len as i32;
771            (*ptr).m_pfnFreeData = Some(free_payload::<P>);
772        }
773        GnsNetworkMessage(ptr, PhantomData)
774            .set_flags(flags)
775            .set_connection(conn)
776    }
777
778    #[inline]
779    pub fn set_connection(self, GnsConnection(conn): GnsConnection) -> Self {
780        unsafe { (*self.0).m_conn = conn }
781        self
782    }
783
784    #[inline]
785    pub fn set_lane(self, lane: GnsLaneId) -> Self {
786        unsafe { (*self.0).m_idxLane = lane }
787        self
788    }
789
790    #[inline]
791    pub fn set_flags(self, flags: SendFlags) -> Self {
792        unsafe { (*self.0).m_nFlags = flags.bits() as _ }
793        self
794    }
795
796    #[inline]
797    pub fn set_user_data(self, userdata: u64) -> Self {
798        unsafe { (*self.0).m_nUserData = userdata as _ }
799        self
800    }
801}
802
803#[repr(transparent)]
804#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
805pub struct GnsConnection(HSteamNetConnection);
806
807impl GnsConnection {
808    /// Wraps a raw `HSteamNetConnection` handle.
809    ///
810    /// GameNetworkingSockets validates the handle when you use it. It rejects
811    /// any handle that does not match a live connection.
812    #[inline]
813    pub const fn from_raw(handle: HSteamNetConnection) -> Self {
814        Self(handle)
815    }
816
817    /// Returns `true` if this is not the invalid-connection value (`0`).
818    #[inline]
819    pub fn is_valid(self) -> bool {
820        self.0 != k_HSteamNetConnection_Invalid
821    }
822}
823
824#[derive(Default, Copy, Clone)]
825pub struct GnsConnectionInfo(SteamNetConnectionInfo_t);
826
827impl GnsConnectionInfo {
828    #[inline]
829    pub fn state(&self) -> ESteamNetworkingConnectionState {
830        self.0.m_eState
831    }
832
833    #[inline]
834    pub fn end_reason(&self) -> u32 {
835        self.0.m_eEndReason as u32
836    }
837
838    #[inline]
839    pub fn end_debug(&self) -> &str {
840        unsafe { CStr::from_ptr(self.0.m_szEndDebug.as_ptr()) }
841            .to_str()
842            .unwrap_or("")
843    }
844
845    #[inline]
846    pub fn remote_address(&self) -> IpAddr {
847        let ipv4 = unsafe { self.0.m_addrRemote.__bindgen_anon_1.m_ipv4 };
848        if ipv4.m_8zeros == 0 && ipv4.m_0000 == 0 && ipv4.m_ffff == 0xffff {
849            IpAddr::from(Ipv4Addr::from(ipv4.m_ip))
850        } else {
851            IpAddr::from(Ipv6Addr::from(unsafe {
852                self.0.m_addrRemote.__bindgen_anon_1.m_ipv6
853            }))
854        }
855    }
856
857    #[inline]
858    pub fn remote_port(&self) -> u16 {
859        self.0.m_addrRemote.m_port
860    }
861}
862
863#[derive(Debug, Default, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
864pub struct GnsConnectionRealTimeLaneStatus(SteamNetConnectionRealTimeLaneStatus_t);
865
866impl GnsConnectionRealTimeLaneStatus {
867    #[inline]
868    pub fn pending_bytes_unreliable(&self) -> u32 {
869        self.0.m_cbPendingUnreliable as _
870    }
871
872    #[inline]
873    pub fn pending_bytes_reliable(&self) -> u32 {
874        self.0.m_cbPendingReliable as _
875    }
876
877    #[inline]
878    pub fn bytes_sent_unacked_reliable(&self) -> u32 {
879        self.0.m_cbSentUnackedReliable as _
880    }
881
882    #[inline]
883    pub fn approximated_queue_time(&self) -> Duration {
884        Duration::from_micros(self.0.m_usecQueueTime as _)
885    }
886}
887
888#[derive(Default, Debug, Copy, Clone, PartialOrd, PartialEq)]
889pub struct GnsConnectionRealTimeStatus(SteamNetConnectionRealTimeStatus_t);
890
891impl GnsConnectionRealTimeStatus {
892    #[inline]
893    pub fn state(&self) -> ESteamNetworkingConnectionState {
894        self.0.m_eState
895    }
896
897    #[inline]
898    pub fn ping(&self) -> u32 {
899        self.0.m_nPing as _
900    }
901
902    #[inline]
903    pub fn quality_local(&self) -> f32 {
904        self.0.m_flConnectionQualityLocal
905    }
906
907    #[inline]
908    pub fn quality_remote(&self) -> f32 {
909        self.0.m_flConnectionQualityRemote
910    }
911
912    #[inline]
913    pub fn out_packets_per_sec(&self) -> f32 {
914        self.0.m_flOutPacketsPerSec
915    }
916
917    #[inline]
918    pub fn out_bytes_per_sec(&self) -> f32 {
919        self.0.m_flOutBytesPerSec
920    }
921
922    #[inline]
923    pub fn in_packets_per_sec(&self) -> f32 {
924        self.0.m_flInPacketsPerSec
925    }
926
927    #[inline]
928    pub fn in_bytes_per_sec(&self) -> f32 {
929        self.0.m_flInBytesPerSec
930    }
931
932    #[inline]
933    pub fn send_rate_bytes_per_sec(&self) -> u32 {
934        self.0.m_nSendRateBytesPerSecond as _
935    }
936
937    #[inline]
938    pub fn pending_bytes_unreliable(&self) -> u32 {
939        self.0.m_cbPendingUnreliable as _
940    }
941
942    #[inline]
943    pub fn pending_bytes_reliable(&self) -> u32 {
944        self.0.m_cbPendingReliable as _
945    }
946
947    #[inline]
948    pub fn bytes_sent_unacked_reliable(&self) -> u32 {
949        self.0.m_cbSentUnackedReliable as _
950    }
951
952    #[inline]
953    pub fn approximated_queue_time(&self) -> Duration {
954        Duration::from_micros(self.0.m_usecQueueTime as _)
955    }
956
957    /// Returns the highest packet jitter seen since the last time you read this
958    /// value. Reading it clears the high water mark.
959    ///
960    /// Returns `None` if no jitter data is available, which happens when the
961    /// underlying value is negative or the connection type does not measure
962    /// jitter.
963    #[inline]
964    pub fn max_jitter_usec(&self) -> Option<i32> {
965        let val = self.0.m_usecMaxJitter;
966        if val < 0 {
967            None
968        } else {
969            Some(val)
970        }
971    }
972}
973
974#[derive(Default, Copy, Clone)]
975pub struct GnsConnectionEvent(SteamNetConnectionStatusChangedCallback_t);
976
977impl GnsConnectionEvent {
978    #[inline]
979    pub fn old_state(&self) -> ESteamNetworkingConnectionState {
980        self.0.m_eOldState
981    }
982
983    #[inline]
984    pub fn connection(&self) -> GnsConnection {
985        GnsConnection(self.0.m_hConn)
986    }
987
988    #[inline]
989    pub fn info(&self) -> GnsConnectionInfo {
990        GnsConnectionInfo(self.0.m_info)
991    }
992}
993
994/// A network socket, and the main type of this library.
995///
996/// Use [`GnsSocket::connect`] to create a client socket and
997/// [`GnsSocket::listen`] to create a server socket. Every operation on a socket
998/// is safe.
999///
1000/// Dropping a socket frees everything that belongs to it. It does not free the
1001/// [`GnsGlobal`] instance.
1002pub struct GnsSocket<S> {
1003    global: &'static GnsGlobal,
1004    state: S,
1005}
1006
1007impl<S> GnsSocket<S>
1008where
1009    S: IsReady,
1010{
1011    /// Returns the status of a connection and of its lanes.
1012    ///
1013    /// Configure the lanes with [`Self::configure_connection_lanes`] before you
1014    /// call this.
1015    pub fn get_connection_real_time_status(
1016        &self,
1017        GnsConnection(conn): GnsConnection,
1018        nb_of_lanes: u32,
1019    ) -> GnsResult<(
1020        GnsConnectionRealTimeStatus,
1021        Vec<GnsConnectionRealTimeLaneStatus>,
1022    )> {
1023        let mut lanes: Vec<GnsConnectionRealTimeLaneStatus> =
1024            vec![Default::default(); nb_of_lanes as _];
1025        let mut status: GnsConnectionRealTimeStatus = Default::default();
1026        check(unsafe {
1027            SteamAPI_ISteamNetworkingSockets_GetConnectionRealTimeStatus(
1028                get_interface(),
1029                conn,
1030                &mut status as *mut GnsConnectionRealTimeStatus
1031                    as *mut SteamNetConnectionRealTimeStatus_t,
1032                nb_of_lanes as _,
1033                lanes.as_mut_ptr() as *mut SteamNetConnectionRealTimeLaneStatus_t,
1034            )
1035        })?;
1036        Ok((status, lanes))
1037    }
1038
1039    pub fn get_connection_info(
1040        &self,
1041        GnsConnection(conn): GnsConnection,
1042    ) -> Option<GnsConnectionInfo> {
1043        let mut info: SteamNetConnectionInfo_t = Default::default();
1044        if unsafe {
1045            SteamAPI_ISteamNetworkingSockets_GetConnectionInfo(get_interface(), conn, &mut info)
1046        } {
1047            Some(GnsConnectionInfo(info))
1048        } else {
1049            None
1050        }
1051    }
1052
1053    pub fn flush_messages_on_connection(
1054        &self,
1055        GnsConnection(conn): GnsConnection,
1056    ) -> GnsResult<()> {
1057        check(unsafe {
1058            SteamAPI_ISteamNetworkingSockets_FlushMessagesOnConnection(get_interface(), conn)
1059        })
1060    }
1061
1062    /// Closes a connection.
1063    ///
1064    /// The wrapper forwards `debug` to the peer when you pass `Some`. Pass
1065    /// `None` to send no diagnostic string and avoid allocating.
1066    ///
1067    /// # Errors
1068    /// Returns [`GnsError::Close`] if the connection handle is invalid, for
1069    /// example because the connection is already closed.
1070    pub fn close_connection(
1071        &self,
1072        GnsConnection(conn): GnsConnection,
1073        reason: u32,
1074        debug: Option<&CStr>,
1075        linger: bool,
1076    ) -> GnsResult<()> {
1077        let debug_ptr = debug.map(|d| d.as_ptr()).unwrap_or(core::ptr::null());
1078        if unsafe {
1079            SteamAPI_ISteamNetworkingSockets_CloseConnection(
1080                get_interface(),
1081                conn,
1082                reason as _,
1083                debug_ptr,
1084                linger,
1085            )
1086        } {
1087            Ok(())
1088        } else {
1089            Err(GnsError::Close)
1090        }
1091    }
1092
1093    /// Receives up to `K` messages and returns an iterator over the ones that
1094    /// were available.
1095    ///
1096    /// Each message is yielded by value, so you can keep it, forward it, or let
1097    /// it drop, which releases it. Any message left in the iterator is released
1098    /// when the iterator is dropped.
1099    ///
1100    /// The `K`-slot pointer buffer lives inline in the returned iterator, so
1101    /// this call allocates nothing and copies no payload. Use
1102    /// [`receive_messages_into`](Self::receive_messages_into) to reuse one
1103    /// buffer across calls and avoid moving the inline array.
1104    ///
1105    /// # Errors
1106    /// Returns [`GnsError::Receive`] if the connection or poll group handle is
1107    /// invalid.
1108    pub fn receive_messages<const K: usize>(&self) -> GnsResult<ReceivedMessages<K>> {
1109        let mut slots: [MessageSlot; K] = [const { MessageSlot::uninit() }; K];
1110        let len = self.state.receive(&mut slots)?;
1111        Ok(ReceivedMessages {
1112            slots,
1113            cursor: SlotCursor { len, pos: 0 },
1114        })
1115    }
1116
1117    /// Receives up to `buffer.len()` messages into a buffer you own, and
1118    /// returns an iterator over the ones that were available.
1119    ///
1120    /// This is the variant of [`receive_messages`](Self::receive_messages) that
1121    /// neither allocates nor moves the buffer. GameNetworkingSockets fills
1122    /// `buffer` in place and the returned iterator borrows it, so reusing one
1123    /// buffer across a polling loop costs nothing per call.
1124    ///
1125    /// # Errors
1126    /// Returns [`GnsError::Receive`] if the connection or poll group handle is
1127    /// invalid.
1128    pub fn receive_messages_into<'a>(
1129        &self,
1130        buffer: &'a mut [MessageSlot],
1131    ) -> GnsResult<ReceivedMessagesInto<'a>> {
1132        let len = self.state.receive(buffer)?;
1133        Ok(ReceivedMessagesInto {
1134            slots: buffer,
1135            cursor: SlotCursor { len, pos: 0 },
1136        })
1137    }
1138
1139    /// Returns an iterator that drains the pending connection events.
1140    ///
1141    /// Unlike [`receive_messages`](Self::receive_messages), you supply no
1142    /// buffer. Events arrive on an internal lock-free queue that the
1143    /// connection-status callback fills, and this call pops from that queue.
1144    pub fn receive_events(&self) -> impl Iterator<Item = GnsConnectionEvent> + '_ {
1145        core::iter::from_fn(|| self.state.queue().pop())
1146    }
1147
1148    pub fn configure_connection_lanes(
1149        &self,
1150        GnsConnection(connection): GnsConnection,
1151        lanes: &[GnsLane],
1152    ) -> GnsResult<()> {
1153        let (priorities, weights): (Vec<i32>, Vec<u16>) =
1154            lanes.iter().map(|l| (l.priority, l.weight)).unzip();
1155        check(unsafe {
1156            SteamAPI_ISteamNetworkingSockets_ConfigureConnectionLanes(
1157                get_interface(),
1158                connection,
1159                lanes.len() as _,
1160                priorities.as_ptr(),
1161                weights.as_ptr(),
1162            )
1163        })
1164    }
1165
1166    /// Sends a single message to its target connection.
1167    ///
1168    /// This is a convenience wrapper over
1169    /// [`send_messages`](Self::send_messages) for the common one-message case.
1170    pub fn send_message(&self, message: GnsNetworkMessage<ToSend>) -> GnsResult<GnsMessageNumber> {
1171        match self.send_messages(core::iter::once(message)).pop() {
1172            Some(SendOutcome::Sent(number)) => Ok(number),
1173            Some(SendOutcome::Failed(result, _)) => Err(GnsError::Api(result)),
1174            // A single message is never `Skipped`, because that only happens
1175            // to a message queued behind an earlier failure on the same
1176            // connection. `send_messages` also returns one outcome per input.
1177            _ => Err(GnsError::Api(EResult::k_EResultFail)),
1178        }
1179    }
1180
1181    /// Sends each message to its target connection.
1182    ///
1183    /// The returned `Vec` holds one [`SendOutcome`] per input message, in the
1184    /// same order.
1185    pub fn send_messages(
1186        &self,
1187        messages: impl IntoIterator<Item = GnsNetworkMessage<ToSend>>,
1188    ) -> Vec<SendOutcome> {
1189        // Pass `bDeleteFailedMessages = false` so that the C library consumes
1190        // the messages it sends and leaves the failed and skipped ones for the
1191        // wrapper to wrap again. `ManuallyDrop` holds off the Rust destructor
1192        // across the FFI call.
1193        let mut raw: Vec<*mut ISteamNetworkingMessage> = messages
1194            .into_iter()
1195            .map(|message| {
1196                let message = core::mem::ManuallyDrop::new(message);
1197                message.0
1198            })
1199            .collect();
1200        let mut result = vec![0i64; raw.len()];
1201        unsafe {
1202            SteamAPI_ISteamNetworkingSockets_SendMessages(
1203                get_interface(),
1204                raw.len() as _,
1205                raw.as_mut_ptr(),
1206                result.as_mut_ptr(),
1207                false,
1208            );
1209        }
1210        result
1211            .into_iter()
1212            .zip(raw)
1213            .map(|(value, ptr)| {
1214                if value > 0 {
1215                    SendOutcome::Sent(value as _)
1216                } else if value < 0 {
1217                    // Sound because gns-sys pins GameNetworkingSockets as a
1218                    // submodule, so the generated `EResult` covers every value
1219                    // the library produces.
1220                    let result = unsafe { core::mem::transmute::<u32, EResult>((-value) as u32) };
1221                    SendOutcome::Failed(result, GnsNetworkMessage(ptr, PhantomData))
1222                } else {
1223                    SendOutcome::Skipped(GnsNetworkMessage(ptr, PhantomData))
1224                }
1225            })
1226            .collect()
1227    }
1228}
1229
1230impl GnsSocket<IsCreated> {
1231    /// The C callback that GameNetworkingSockets invokes on a connection-state
1232    /// change.
1233    ///
1234    /// The wrapper stores the queue ID in the connection user data, which is
1235    /// how this callback finds the right queue in [`GnsGlobal`].
1236    unsafe extern "C" fn on_connection_state_changed(
1237        info: &mut SteamNetConnectionStatusChangedCallback_t,
1238    ) {
1239        let gns_global = GnsGlobal::get()
1240            // Reaching this point at all means GnsGlobal is initialized.
1241            .expect("GnsGlobal should be initialized");
1242
1243        let queue_id = info.m_info.m_nUserData as _;
1244        // Fast path: take the read lock, look up the queue, and push if the
1245        // weak reference still upgrades.
1246        let needs_purge = {
1247            let queues = gns_global.event_queues.read().unwrap();
1248            match queues.get(&queue_id).and_then(Weak::upgrade) {
1249                Some(queue) => {
1250                    queue.push(GnsConnectionEvent(*info));
1251                    false
1252                }
1253                None => queues.contains_key(&queue_id),
1254            }
1255        };
1256        // Slow path: the socket was dropped while this callback ran, so the
1257        // entry is still in the map but the queue is gone. Take the write lock
1258        // to remove it. Queue IDs are never reused, so removing a key that
1259        // another thread already removed does no harm.
1260        if needs_purge {
1261            gns_global.event_queues.write().unwrap().remove(&queue_id);
1262        }
1263    }
1264
1265    /// Creates a socket in the [`IsCreated`] state.
1266    #[inline]
1267    pub fn new(global: &'static GnsGlobal) -> Self {
1268        GnsSocket {
1269            global,
1270            state: IsCreated,
1271        }
1272    }
1273
1274    fn setup_common(
1275        address: IpAddr,
1276        port: u16,
1277        queue_id: int64,
1278    ) -> (SteamNetworkingIPAddr, [SteamNetworkingConfigValue_t; 2]) {
1279        let addr = SteamNetworkingIPAddr {
1280            __bindgen_anon_1: match address {
1281                IpAddr::V4(address) => SteamNetworkingIPAddr__bindgen_ty_2 {
1282                    m_ipv4: SteamNetworkingIPAddr_IPv4MappedAddress {
1283                        m_8zeros: 0,
1284                        m_0000: 0,
1285                        m_ffff: 0xffff,
1286                        m_ip: address.octets(),
1287                    },
1288                },
1289                IpAddr::V6(address) => SteamNetworkingIPAddr__bindgen_ty_2 {
1290                    m_ipv6: address.octets(),
1291                },
1292            },
1293            m_port: port,
1294        };
1295        let options = [SteamNetworkingConfigValue_t {
1296            m_eDataType: ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Ptr,
1297            m_eValue: ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_Callback_ConnectionStatusChanged,
1298            m_val: SteamNetworkingConfigValue_t__bindgen_ty_1 {
1299              m_ptr: Self::on_connection_state_changed as *const fn(&SteamNetConnectionStatusChangedCallback_t) as *mut c_void
1300            }
1301          }, SteamNetworkingConfigValue_t {
1302            m_eDataType: ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Int64,
1303            m_eValue: ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_ConnectionUserData,
1304            m_val: SteamNetworkingConfigValue_t__bindgen_ty_1 {
1305              m_int64: queue_id
1306            }
1307        }];
1308        (addr, options)
1309    }
1310
1311    /// Listens for incoming connections.
1312    ///
1313    /// This moves the socket from [`IsCreated`] to [`IsServer`], which gives
1314    /// you the server operations.
1315    pub fn listen(self, address: IpAddr, port: u16) -> GnsResult<GnsSocket<IsServer>> {
1316        let (queue_id, queue) = self.global.create_queue();
1317        let (addr, options) = Self::setup_common(address, port, queue_id);
1318        let listen_socket = unsafe {
1319            SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP(
1320                get_interface(),
1321                &addr,
1322                options.len() as _,
1323                options.as_ptr(),
1324            )
1325        };
1326        if listen_socket == k_HSteamListenSocket_Invalid {
1327            Err(GnsError::Listen)
1328        } else {
1329            let poll_group =
1330                unsafe { SteamAPI_ISteamNetworkingSockets_CreatePollGroup(get_interface()) };
1331            if poll_group == k_HSteamNetPollGroup_Invalid {
1332                Err(GnsError::Listen)
1333            } else {
1334                Ok(GnsSocket {
1335                    global: self.global,
1336                    state: IsServer {
1337                        queue,
1338                        queue_id,
1339                        global: self.global,
1340                        listen_socket: GnsListenSocket(listen_socket),
1341                        poll_group: GnsPollGroup(poll_group),
1342                    },
1343                })
1344            }
1345        }
1346    }
1347
1348    /// Connects to a remote host.
1349    ///
1350    /// This moves the socket from [`IsCreated`] to [`IsClient`], which gives
1351    /// you the client operations.
1352    pub fn connect(self, address: IpAddr, port: u16) -> GnsResult<GnsSocket<IsClient>> {
1353        let (queue_id, queue) = self.global.create_queue();
1354        let (addr, options) = Self::setup_common(address, port, queue_id);
1355        let connection = unsafe {
1356            SteamAPI_ISteamNetworkingSockets_ConnectByIPAddress(
1357                get_interface(),
1358                &addr,
1359                options.len() as _,
1360                options.as_ptr(),
1361            )
1362        };
1363        if connection == k_HSteamNetConnection_Invalid {
1364            Err(GnsError::Connect)
1365        } else {
1366            Ok(GnsSocket {
1367                global: self.global,
1368                state: IsClient {
1369                    queue,
1370                    queue_id,
1371                    global: self.global,
1372                    connection: GnsConnection(connection),
1373                },
1374            })
1375        }
1376    }
1377}
1378
1379impl GnsSocket<IsServer> {
1380    /// Accepts an incoming connection. Only a socket in the [`IsServer`] state
1381    /// has this operation.
1382    pub fn accept(&self, connection: GnsConnection) -> GnsResult<()> {
1383        check(unsafe {
1384            SteamAPI_ISteamNetworkingSockets_AcceptConnection(get_interface(), connection.0)
1385        })?;
1386        if !unsafe {
1387            SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup(
1388                get_interface(),
1389                connection.0,
1390                self.state.poll_group.0,
1391            )
1392        } {
1393            // The poll group and the connection should both be valid here, so
1394            // this is not expected to happen.
1395            return Err(GnsError::Accept);
1396        }
1397        Ok(())
1398    }
1399}
1400
1401impl GnsSocket<IsClient> {
1402    /// Returns the socket connection. Only a socket in the [`IsClient`] state
1403    /// has this operation.
1404    #[inline]
1405    pub fn connection(&self) -> GnsConnection {
1406        self.state.connection
1407    }
1408}
1409
1410/// A configuration value for [`GnsUtils::set_global_config_value`] and
1411/// [`GnsUtils::set_connection_config_value`].
1412pub enum GnsConfig<'a> {
1413    Float(f32),
1414    Int32(i32),
1415    /// Allocates a `CString` so that the value ends in a NUL byte. Use
1416    /// [`GnsConfig::CStr`] to skip that allocation when you already hold a
1417    /// `CStr`.
1418    String(&'a str),
1419    /// A string variant that does not allocate, because `&CStr` already ends
1420    /// in a NUL byte.
1421    CStr(&'a CStr),
1422    Ptr(*mut c_void),
1423}
1424
1425pub struct GnsUtils(());
1426
1427type MsgPtr = *const ::std::os::raw::c_char;
1428
1429/// A debug callback that you supply.
1430///
1431/// It must be `Send + Sync` because GameNetworkingSockets invokes it from its
1432/// service thread, and it may capture state that your own threads share.
1433type DebugCallback = dyn Fn(ESteamNetworkingSocketsDebugOutputType, &str) + Send + Sync + 'static;
1434
1435/// Holds the callback that [`GnsUtils::enable_debug_output`] installs.
1436///
1437/// GameNetworkingSockets invokes it from its service thread, so this `OnceLock`
1438/// is the synchronization point.
1439static DEBUG_CB: OnceLock<Box<DebugCallback>> = OnceLock::new();
1440
1441unsafe extern "C" fn debug_trampoline(ty: ESteamNetworkingSocketsDebugOutputType, msg: MsgPtr) {
1442    if let Some(cb) = DEBUG_CB.get() {
1443        let s = unsafe { CStr::from_ptr(msg) }.to_str().unwrap_or("");
1444        cb(ty, s);
1445    }
1446}
1447
1448impl GnsUtils {
1449    /// Installs a debug callback.
1450    ///
1451    /// Only the first call takes effect. Later calls are ignored.
1452    ///
1453    /// GameNetworkingSockets runs the callback on its service thread, which is
1454    /// why the callback must be `Send + Sync + 'static`. The callback may
1455    /// capture state, because the wrapper stores it as a boxed closure. The
1456    /// `&str` is borrowed only for the duration of the call.
1457    pub fn enable_debug_output(
1458        &self,
1459        ty: ESteamNetworkingSocketsDebugOutputType,
1460        f: impl Fn(ESteamNetworkingSocketsDebugOutputType, &str) + Send + Sync + 'static,
1461    ) {
1462        let _ = DEBUG_CB.set(Box::new(f));
1463        unsafe {
1464            SteamAPI_ISteamNetworkingUtils_SetDebugOutputFunction(
1465                get_utils(),
1466                ty,
1467                Some(debug_trampoline),
1468            );
1469        }
1470    }
1471
1472    /// Allocates an outbound message and takes ownership of `payload`.
1473    ///
1474    /// The buffer stays alive until GameNetworkingSockets releases the message.
1475    /// At that point the wrapper rebuilds `P` with [`Payload::from_raw`] and
1476    /// drops it. Nothing is copied when the payload already owns heap memory.
1477    #[inline]
1478    pub fn allocate_message<P: Payload>(
1479        &self,
1480        conn: GnsConnection,
1481        flags: SendFlags,
1482        payload: P,
1483    ) -> GnsNetworkMessage<ToSend> {
1484        let message_ptr = unsafe { SteamAPI_ISteamNetworkingUtils_AllocateMessage(get_utils(), 0) };
1485        GnsNetworkMessage::new(message_ptr, conn, flags, payload)
1486    }
1487
1488    /// Sets a global configuration value, for example
1489    /// `k_ESteamNetworkingConfig_FakePacketLag_Send` to 1000 ms.
1490    pub fn set_global_config_value(
1491        &self,
1492        typ: ESteamNetworkingConfigValue,
1493        value: GnsConfig<'_>,
1494    ) -> GnsResult<()> {
1495        let result = match value {
1496            GnsConfig::Float(x) => unsafe {
1497                SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueFloat(get_utils(), typ, x)
1498            },
1499            GnsConfig::Int32(x) => unsafe {
1500                SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueInt32(get_utils(), typ, x)
1501            },
1502            GnsConfig::String(x) => {
1503                let c = CString::new(x).map_err(|_| GnsError::Config("interior NUL"))?;
1504                unsafe {
1505                    SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueString(
1506                        get_utils(),
1507                        typ,
1508                        c.as_ptr(),
1509                    )
1510                }
1511            }
1512            GnsConfig::CStr(x) => unsafe {
1513                SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueString(
1514                    get_utils(),
1515                    typ,
1516                    x.as_ptr(),
1517                )
1518            },
1519            GnsConfig::Ptr(x) => unsafe {
1520                SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValuePtr(get_utils(), typ, x)
1521            },
1522        };
1523        if result {
1524            Ok(())
1525        } else {
1526            Err(GnsError::Config("SetGlobalConfigValue rejected"))
1527        }
1528    }
1529
1530    /// Sets a configuration value on one connection, for example
1531    /// `k_ESteamNetworkingConfig_SendRateMin` on an accepted connection.
1532    pub fn set_connection_config_value(
1533        &self,
1534        conn: GnsConnection,
1535        typ: ESteamNetworkingConfigValue,
1536        value: GnsConfig<'_>,
1537    ) -> GnsResult<()> {
1538        let result = match value {
1539            GnsConfig::Float(x) => unsafe {
1540                SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueFloat(
1541                    get_utils(),
1542                    conn.0,
1543                    typ,
1544                    x,
1545                )
1546            },
1547            GnsConfig::Int32(x) => unsafe {
1548                SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueInt32(
1549                    get_utils(),
1550                    conn.0,
1551                    typ,
1552                    x,
1553                )
1554            },
1555            GnsConfig::String(x) => {
1556                let c = CString::new(x).map_err(|_| GnsError::Config("interior NUL"))?;
1557                unsafe {
1558                    SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueString(
1559                        get_utils(),
1560                        conn.0,
1561                        typ,
1562                        c.as_ptr(),
1563                    )
1564                }
1565            }
1566            GnsConfig::CStr(x) => unsafe {
1567                SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueString(
1568                    get_utils(),
1569                    conn.0,
1570                    typ,
1571                    x.as_ptr(),
1572                )
1573            },
1574            GnsConfig::Ptr(_) => return Err(GnsError::Config("Ptr not supported per-connection")),
1575        };
1576        if result {
1577            Ok(())
1578        } else {
1579            Err(GnsError::Config("SetConnectionConfigValue rejected"))
1580        }
1581    }
1582}