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///
102/// The enum is non-exhaustive because wrapping more of the underlying API
103/// adds variants.
104#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
105#[non_exhaustive]
106pub enum GnsError {
107    #[error("GameNetworkingSockets_Init failed: {0}")]
108    Init(String),
109    #[error("listen failed: invalid handle")]
110    Listen,
111    #[error("connect failed: invalid handle")]
112    Connect,
113    #[error("create socket pair failed")]
114    SocketPair,
115    #[error("receive failed: invalid connection or poll group handle")]
116    Receive,
117    #[error("accept failed: could not set connection poll group")]
118    Accept,
119    #[error("close failed: invalid connection handle")]
120    Close,
121    #[error("steam api: {0:?}")]
122    Api(EResult),
123    #[error("config: {0}")]
124    Config(&'static str),
125}
126
127pub type GnsResult<T> = Result<T, GnsError>;
128
129/// Converts a `SteamNetworkingIPAddr` into a Rust [`IpAddr`], unmapping
130/// IPv4-mapped IPv6 addresses back to plain IPv4.
131#[inline]
132fn ip_from_steam_ip_addr(addr: &SteamNetworkingIPAddr) -> IpAddr {
133    let ipv4 = unsafe { addr.__bindgen_anon_1.m_ipv4 };
134    if ipv4.m_8zeros == 0 && ipv4.m_0000 == 0 && ipv4.m_ffff == 0xffff {
135        IpAddr::from(Ipv4Addr::from(ipv4.m_ip))
136    } else {
137        IpAddr::from(Ipv6Addr::from(unsafe { addr.__bindgen_anon_1.m_ipv6 }))
138    }
139}
140
141/// Converts an `EResult` returned by an FFI call into a [`GnsResult`].
142#[inline]
143fn check(e: EResult) -> GnsResult<()> {
144    match e {
145        EResult::k_EResultOK => Ok(()),
146        e => Err(GnsError::Api(e)),
147    }
148}
149
150/// Owns the initialization and teardown of GameNetworkingSockets and its
151/// singletons.
152///
153/// Call [`GnsGlobal::get()`] to obtain the instance. The first call initializes
154/// GameNetworkingSockets, and later calls return the same instance.
155pub struct GnsGlobal {
156    utils: GnsUtils,
157    next_queue_id: AtomicI64,
158    /// Maps each socket to its event queue.
159    ///
160    /// Reads dominate: every connection-state callback from the
161    /// GameNetworkingSockets service thread performs one lookup. Writes happen
162    /// only when a socket is created or dropped, or in the rare case where a
163    /// callback arrives for a socket that was dropped moments earlier. An
164    /// `RwLock` lets those reads run concurrently.
165    event_queues: RwLock<HashMap<i64, Weak<SegQueue<GnsConnectionEvent>>>>,
166}
167
168static GNS_GLOBAL: OnceLock<GnsGlobal> = OnceLock::new();
169
170impl Drop for GnsGlobal {
171    #[inline]
172    fn drop(&mut self) {
173        // Stop the service thread and tear down the internal state.
174        //
175        // GameNetworkingSockets does not support an init, kill, init cycle on
176        // every version, so this runs only when the singleton itself is
177        // dropped.
178        unsafe { GameNetworkingSockets_Kill() }
179    }
180}
181
182impl GnsGlobal {
183    /// Returns a reference to the [`GnsGlobal`] instance.
184    ///
185    /// The first call initializes GameNetworkingSockets through
186    /// [`sys::GameNetworkingSockets_Init`]. Later calls return the instance
187    /// that call created.
188    ///
189    /// # Errors
190    /// Returns [`GnsError::Init`] with the message that GameNetworkingSockets
191    /// produced if initialization fails.
192    pub fn get() -> GnsResult<&'static Self> {
193        // Fast path: no lock
194        if let Some(g) = GNS_GLOBAL.get() {
195            return Ok(g);
196        }
197        // use get_or_try_init once stabilized: https://github.com/rust-lang/rust/issues/109737
198        static INIT_LOCK: Mutex<()> = Mutex::new(());
199        let _guard = INIT_LOCK.lock().unwrap();
200        if let Some(g) = GNS_GLOBAL.get() {
201            return Ok(g);
202        }
203        unsafe {
204            let mut error: SteamDatagramErrMsg = MaybeUninit::zeroed().assume_init();
205            if !GameNetworkingSockets_Init(core::ptr::null(), &mut error) {
206                return Err(GnsError::Init(
207                    CStr::from_ptr(error.as_ptr())
208                        .to_str()
209                        .unwrap_or("")
210                        .to_owned(),
211                ));
212            }
213        }
214        let _ = GNS_GLOBAL.set(GnsGlobal {
215            utils: GnsUtils(()),
216            next_queue_id: AtomicI64::new(0),
217            event_queues: RwLock::new(HashMap::new()),
218        });
219        Ok(GNS_GLOBAL.get().expect("impossible; qed;"))
220    }
221
222    #[inline]
223    pub fn poll_callbacks(&self) {
224        unsafe {
225            SteamAPI_ISteamNetworkingSockets_RunCallbacks(get_interface());
226        }
227    }
228
229    #[inline]
230    pub fn utils(&self) -> &GnsUtils {
231        &self.utils
232    }
233
234    #[inline]
235    pub fn queue_count(&self) -> usize {
236        self.event_queues.read().unwrap().len()
237    }
238
239    #[inline]
240    fn create_queue(&self) -> (i64, Arc<SegQueue<GnsConnectionEvent>>) {
241        let queue = Arc::new(SegQueue::new());
242        let queue_id = self.next_queue_id.fetch_add(1, Ordering::SeqCst);
243        self.event_queues
244            .write()
245            .unwrap()
246            .insert(queue_id, Arc::downgrade(&queue));
247        (queue_id, queue)
248    }
249}
250
251/// An opaque wrapper around [`sys::HSteamListenSocket`].
252#[repr(transparent)]
253pub(crate) struct GnsListenSocket(HSteamListenSocket);
254
255/// An opaque wrapper around [`sys::HSteamNetPollGroup`].
256#[repr(transparent)]
257pub(crate) struct GnsPollGroup(HSteamNetPollGroup);
258
259/// The initial state of a [`GnsSocket`].
260///
261/// A socket in this state is neither a client nor a server yet, so it holds no
262/// data.
263pub struct IsCreated;
264
265mod private {
266    pub trait Sealed {}
267    impl Sealed for super::IsServer {}
268    impl Sealed for super::IsClient {}
269}
270
271/// The operations that every ready [`GnsSocket`] supports.
272///
273/// A ready socket is either a client or a server. Both can read connection
274/// events and receive messages.
275pub trait IsReady: private::Sealed {
276    /// Returns the connection event queue. The queue is thread-safe.
277    fn queue(&self) -> &SegQueue<GnsConnectionEvent>;
278    /// Receives up to `slots.len()` messages into `slots`.
279    ///
280    /// Returns the number of slots that GameNetworkingSockets filled, or
281    /// [`GnsError::Receive`] if the underlying handle is invalid.
282    fn receive(&self, slots: &mut [MaybeUninit<*mut ISteamNetworkingMessage>]) -> GnsResult<usize>;
283}
284
285/// The state of a [`GnsSocket`] that acts as a server, normally reached
286/// through [`GnsSocket::listen`].
287///
288/// In this state the socket holds what it needs to accept connections and poll
289/// them for messages.
290pub struct IsServer {
291    queue: Arc<SegQueue<GnsConnectionEvent>>,
292    queue_id: i64,
293    global: &'static GnsGlobal,
294    listen_socket: GnsListenSocket,
295    poll_group: GnsPollGroup,
296}
297
298impl Drop for IsServer {
299    #[inline]
300    fn drop(&mut self) {
301        unsafe {
302            SteamAPI_ISteamNetworkingSockets_CloseListenSocket(
303                get_interface(),
304                self.listen_socket.0,
305            );
306            SteamAPI_ISteamNetworkingSockets_DestroyPollGroup(get_interface(), self.poll_group.0);
307        }
308        self.global
309            .event_queues
310            .write()
311            .unwrap()
312            .remove(&self.queue_id);
313    }
314}
315
316impl IsReady for IsServer {
317    #[inline]
318    fn queue(&self) -> &SegQueue<GnsConnectionEvent> {
319        &self.queue
320    }
321
322    fn receive(&self, slots: &mut [MaybeUninit<*mut ISteamNetworkingMessage>]) -> GnsResult<usize> {
323        let result = unsafe {
324            SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnPollGroup(
325                get_interface(),
326                self.poll_group.0,
327                slots.as_mut_ptr() as _,
328                slots.len() as _,
329            ) as _
330        };
331        if result == usize::MAX {
332            Err(GnsError::Receive)
333        } else {
334            Ok(result)
335        }
336    }
337}
338
339/// The state of a [`GnsSocket`] that acts as a client, normally reached
340/// through [`GnsSocket::connect`].
341///
342/// In this state the socket holds what it needs to send and receive messages.
343pub struct IsClient {
344    queue: Arc<SegQueue<GnsConnectionEvent>>,
345    queue_id: i64,
346    global: &'static GnsGlobal,
347    connection: GnsConnection,
348}
349
350impl Drop for IsClient {
351    fn drop(&mut self) {
352        unsafe {
353            SteamAPI_ISteamNetworkingSockets_CloseConnection(
354                get_interface(),
355                self.connection.0,
356                0,
357                core::ptr::null(),
358                false,
359            );
360        }
361        self.global
362            .event_queues
363            .write()
364            .unwrap()
365            .remove(&self.queue_id);
366    }
367}
368
369impl IsReady for IsClient {
370    #[inline]
371    fn queue(&self) -> &SegQueue<GnsConnectionEvent> {
372        &self.queue
373    }
374
375    fn receive(&self, slots: &mut [MaybeUninit<*mut ISteamNetworkingMessage>]) -> GnsResult<usize> {
376        let result = unsafe {
377            SteamAPI_ISteamNetworkingSockets_ReceiveMessagesOnConnection(
378                get_interface(),
379                self.connection.0,
380                slots.as_mut_ptr() as _,
381                slots.len() as _,
382            ) as _
383        };
384        if result == usize::MAX {
385            Err(GnsError::Receive)
386        } else {
387            Ok(result)
388        }
389    }
390}
391
392pub struct ToReceive(());
393
394pub struct ToSend(());
395
396/// A single receive slot.
397///
398/// Each slot is an uninitialized cell that GameNetworkingSockets fills with one
399/// `*mut ISteamNetworkingMessage`. Build a buffer of slots, for example
400/// `[const { MessageSlot::uninit() }; 128]`, and pass it to
401/// [`GnsSocket::receive_messages_into`].
402pub type MessageSlot = MaybeUninit<*mut ISteamNetworkingMessage>;
403
404/// Rebuilds the owned message stored in `slot`.
405///
406/// # Safety
407/// GameNetworkingSockets must have initialized `slot`, meaning the slot lies
408/// within the prefix length that `receive` reported. The slot must not have
409/// been taken already, otherwise the message is released more than once.
410#[inline]
411unsafe fn take_message(slot: &MessageSlot) -> GnsNetworkMessage<ToReceive> {
412    GnsNetworkMessage(unsafe { slot.assume_init() }, PhantomData)
413}
414
415/// Tracks progress through a buffer of receive slots.
416///
417/// The slots in `slots[..len]` are initialized, and `pos` is the next slot to
418/// hand out. This type holds the unsafe take and release logic in one place so
419/// that the owning and borrowing iterators cannot drift apart.
420struct SlotCursor {
421    len: usize,
422    pos: usize,
423}
424
425impl SlotCursor {
426    fn next(&mut self, slots: &[MessageSlot]) -> Option<GnsNetworkMessage<ToReceive>> {
427        if self.pos < self.len {
428            // Safety: GameNetworkingSockets initialized `slots[..len]`, and
429            // `pos` only increases, so each slot is taken at most once.
430            let message = unsafe { take_message(&slots[self.pos]) };
431            self.pos += 1;
432            Some(message)
433        } else {
434            None
435        }
436    }
437
438    #[inline]
439    fn remaining(&self) -> usize {
440        self.len - self.pos
441    }
442
443    /// Releases every slot that was not handed out. Safe to call more than
444    /// once.
445    fn drain_unconsumed(&mut self, slots: &[MessageSlot]) {
446        for slot in &slots[self.pos..self.len] {
447            // Safety: same invariant as `next`. These slots are initialized
448            // and were never handed out, so each is released exactly once.
449            drop(unsafe { take_message(slot) });
450        }
451        self.pos = self.len;
452    }
453}
454
455/// An iterator over the messages from one [`GnsSocket::receive_messages`]
456/// call.
457///
458/// The iterator owns its `K`-slot pointer buffer inline, so it performs no heap
459/// allocation. It yields each [`GnsNetworkMessage<ToReceive>`] by value, and
460/// releases any message you did not consume when it is dropped.
461///
462/// See [`GnsSocket::receive_messages_into`] for a variant that borrows a buffer
463/// you own, which also avoids moving the inline array.
464pub struct ReceivedMessages<const K: usize> {
465    slots: [MessageSlot; K],
466    cursor: SlotCursor,
467}
468
469impl<const K: usize> Iterator for ReceivedMessages<K> {
470    type Item = GnsNetworkMessage<ToReceive>;
471
472    #[inline]
473    fn next(&mut self) -> Option<Self::Item> {
474        self.cursor.next(&self.slots)
475    }
476
477    #[inline]
478    fn size_hint(&self) -> (usize, Option<usize>) {
479        let remaining = self.cursor.remaining();
480        (remaining, Some(remaining))
481    }
482}
483
484impl<const K: usize> ExactSizeIterator for ReceivedMessages<K> {}
485
486impl<const K: usize> core::iter::FusedIterator for ReceivedMessages<K> {}
487
488impl<const K: usize> Drop for ReceivedMessages<K> {
489    #[inline]
490    fn drop(&mut self) {
491        self.cursor.drain_unconsumed(&self.slots);
492    }
493}
494
495/// An iterator returned by [`GnsSocket::receive_messages_into`].
496///
497/// The iterator borrows your buffer for its whole lifetime, so you cannot reuse
498/// the buffer while messages are still outstanding. It yields each
499/// [`GnsNetworkMessage<ToReceive>`] by value.
500///
501/// Nothing is allocated and the pointer buffer never moves. Only the individual
502/// message pointers move. Any message you did not consume is released when the
503/// iterator is dropped.
504pub struct ReceivedMessagesInto<'a> {
505    slots: &'a mut [MessageSlot],
506    cursor: SlotCursor,
507}
508
509impl Iterator for ReceivedMessagesInto<'_> {
510    type Item = GnsNetworkMessage<ToReceive>;
511
512    #[inline]
513    fn next(&mut self) -> Option<Self::Item> {
514        self.cursor.next(self.slots)
515    }
516
517    #[inline]
518    fn size_hint(&self) -> (usize, Option<usize>) {
519        let remaining = self.cursor.remaining();
520        (remaining, Some(remaining))
521    }
522}
523
524impl ExactSizeIterator for ReceivedMessagesInto<'_> {}
525
526impl core::iter::FusedIterator for ReceivedMessagesInto<'_> {}
527
528impl Drop for ReceivedMessagesInto<'_> {
529    #[inline]
530    fn drop(&mut self) {
531        self.cursor.drain_unconsumed(self.slots);
532    }
533}
534
535bitflags::bitflags! {
536    /// A type-safe wrapper over the `k_nSteamNetworkingSend_*` flags.
537    ///
538    /// The bit values match the raw `c_int` constants.
539    #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
540    pub struct SendFlags: i32 {
541        const UNRELIABLE                  = sys::k_nSteamNetworkingSend_Unreliable;
542        const NO_NAGLE                    = sys::k_nSteamNetworkingSend_NoNagle;
543        const NO_DELAY                    = sys::k_nSteamNetworkingSend_NoDelay;
544        const RELIABLE                    = sys::k_nSteamNetworkingSend_Reliable;
545        const USE_CURRENT_THREAD          = sys::k_nSteamNetworkingSend_UseCurrentThread;
546        const AUTO_RESTART_BROKEN_SESSION = sys::k_nSteamNetworkingSend_AutoRestartBrokenSession;
547    }
548}
549
550/// A connection lane.
551///
552/// `priority` is a signed C `int` where a lower value means a higher priority.
553/// `weight` is the relative scheduling weight within one priority class.
554#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
555pub struct GnsLane {
556    pub priority: i32,
557    pub weight: u16,
558}
559
560impl GnsLane {
561    #[inline]
562    pub const fn new(priority: i32, weight: u16) -> Self {
563        Self { priority, weight }
564    }
565}
566
567/// A lane identifier.
568pub type GnsLaneId = u16;
569
570/// The result of one message in a [`GnsSocket::send_messages`] batch.
571///
572/// `Skipped` mirrors how GameNetworkingSockets handles a failed batch. Once a
573/// message fails on a connection, every later message in the same batch that
574/// targets that connection is skipped without being attempted, and its result
575/// is reported as `0`. A skipped message keeps its payload, so the wrapper
576/// returns it to you, the same way it returns a `Failed` message.
577#[must_use = "Failed/Skipped variants own a message that needs inspection or drop"]
578pub enum SendOutcome {
579    Sent(GnsMessageNumber),
580    Failed(EResult, GnsNetworkMessage<ToSend>),
581    Skipped(GnsNetworkMessage<ToSend>),
582}
583
584/// An owned byte buffer for an outbound message.
585///
586/// GameNetworkingSockets reads `m_pData` on its service thread after
587/// `SendMessages` returns, so a message must own its bytes until
588/// GameNetworkingSockets releases it.
589///
590/// [`into_raw`](Self::into_raw) returns a `(pointer, length)` pair that the
591/// wrapper stores unchanged in `m_pData` and `m_cbSize`. When
592/// GameNetworkingSockets releases the message, the wrapper passes those same
593/// values to [`from_raw`](Self::from_raw) to rebuild `Self`, then drops the
594/// result.
595///
596/// This mirrors `Box::into_raw` and `Box::from_raw`, so you can express how the
597/// buffer is freed with an ordinary Rust `Drop` implementation.
598///
599/// # Safety
600/// `from_raw(p, n)` must be sound whenever `(p, n)` came from an earlier
601/// `into_raw` call on the same implementation. In other words, `from_raw` must
602/// undo `into_raw` exactly.
603///
604/// `into_raw` must not run the `Drop` implementation of `Self`, because
605/// ownership passes to GameNetworkingSockets.
606pub unsafe trait Payload: Send + 'static {
607    fn into_raw(self) -> (*mut u8, usize);
608    /// # Safety
609    /// `ptr` and `len` must be the values that an earlier
610    /// [`into_raw`](Self::into_raw) call on this same implementation returned,
611    /// and that ownership must not have been reclaimed already.
612    unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self;
613}
614
615/// The `m_pfnFreeData` callback that the wrapper installs on every
616/// `GnsNetworkMessage<ToSend>`.
617///
618/// It reads `m_pData` and `m_cbSize`, rebuilds `P` with
619/// [`Payload::from_raw`], and drops the result.
620extern "C" fn free_payload<P: Payload>(msg: *mut ISteamNetworkingMessage) {
621    let ptr = unsafe { (*msg).m_pData } as *mut u8;
622    let len = unsafe { (*msg).m_cbSize } as usize;
623    // Safety: `GnsNetworkMessage::<ToSend>::new` wrote `ptr` and `len` from
624    // `P::into_raw`, and GameNetworkingSockets releases each message once.
625    drop(unsafe { P::from_raw(ptr, len) });
626}
627
628unsafe impl Payload for Box<[u8]> {
629    #[inline]
630    fn into_raw(self) -> (*mut u8, usize) {
631        let len = self.len();
632        let raw = Box::into_raw(self) as *mut u8;
633        (raw, len)
634    }
635    #[inline]
636    unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self {
637        let slice = core::ptr::slice_from_raw_parts_mut(ptr, len);
638        unsafe { Box::from_raw(slice) }
639    }
640}
641
642// This goes through `Box<[u8]>`. `into_boxed_slice` shrinks the buffer to fit,
643// which costs one reallocation when the capacity differs from the length, so
644// the pointer and length are enough to rebuild the value.
645unsafe impl Payload for Vec<u8> {
646    #[inline]
647    fn into_raw(self) -> (*mut u8, usize) {
648        <Box<[u8]> as Payload>::into_raw(self.into_boxed_slice())
649    }
650    #[inline]
651    unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self {
652        unsafe { Vec::from_raw_parts(ptr, len, len) }
653    }
654}
655
656unsafe impl Payload for String {
657    #[inline]
658    fn into_raw(self) -> (*mut u8, usize) {
659        <Vec<u8> as Payload>::into_raw(self.into_bytes())
660    }
661    #[inline]
662    unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self {
663        unsafe { String::from_raw_parts(ptr, len, len) }
664    }
665}
666
667unsafe impl Payload for Arc<[u8]> {
668    #[inline]
669    fn into_raw(self) -> (*mut u8, usize) {
670        let len = self.len();
671        let raw = Arc::into_raw(self) as *const u8 as *mut u8;
672        (raw, len)
673    }
674    #[inline]
675    unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self {
676        let slice = core::ptr::slice_from_raw_parts(ptr as *const u8, len);
677        unsafe { Arc::from_raw(slice) }
678    }
679}
680
681unsafe impl Payload for &'static [u8] {
682    #[inline]
683    fn into_raw(self) -> (*mut u8, usize) {
684        (self.as_ptr() as *mut u8, self.len())
685    }
686    #[inline]
687    unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self {
688        unsafe { core::slice::from_raw_parts(ptr as *const u8, len) }
689    }
690}
691
692unsafe impl Payload for &'static str {
693    #[inline]
694    fn into_raw(self) -> (*mut u8, usize) {
695        (self.as_ptr() as *mut u8, self.len())
696    }
697    #[inline]
698    unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self {
699        let bytes = unsafe { core::slice::from_raw_parts(ptr as *const u8, len) };
700        unsafe { core::str::from_utf8_unchecked(bytes) }
701    }
702}
703
704/// A GameNetworkingSockets message, tagged with its direction.
705///
706/// The library produces `ToReceive` messages. You create `ToSend` messages with
707/// [`GnsUtils::allocate_message`], and they own their payload through
708/// [`Payload`]. Both kinds are released when dropped.
709#[repr(transparent)]
710pub struct GnsNetworkMessage<T>(*mut ISteamNetworkingMessage, PhantomData<T>);
711
712impl<T> Drop for GnsNetworkMessage<T> {
713    #[inline]
714    fn drop(&mut self) {
715        if !self.0.is_null() {
716            unsafe {
717                SteamAPI_SteamNetworkingMessage_t_Release(self.0);
718            }
719        }
720    }
721}
722
723impl<T> GnsNetworkMessage<T> {
724    /// Returns the raw `*mut ISteamNetworkingMessage` and forgets the wrapper.
725    ///
726    /// # Safety
727    /// You take over releasing the message, for example by calling
728    /// `SteamAPI_SteamNetworkingMessage_t_Release`. For a `ToSend` message that
729    /// release also runs the `m_pfnFreeData` callback that [`Payload`]
730    /// installed.
731    #[inline]
732    pub unsafe fn into_inner(self) -> *mut ISteamNetworkingMessage {
733        // Hold off the wrapper's destructor. Releasing the message is now
734        // the caller's job. Without this the message would be released here
735        // and the returned pointer would dangle.
736        core::mem::ManuallyDrop::new(self).0
737    }
738
739    #[inline]
740    pub fn payload(&self) -> &[u8] {
741        unsafe {
742            core::slice::from_raw_parts((*self.0).m_pData as *const u8, (*self.0).m_cbSize as _)
743        }
744    }
745
746    #[inline]
747    pub fn message_number(&self) -> u64 {
748        unsafe { (*self.0).m_nMessageNumber as _ }
749    }
750
751    /// Returns the local timestamp at which the message arrived, in
752    /// microseconds.
753    ///
754    /// The value shares its timebase with [`GnsUtils::local_timestamp`], so
755    /// compare the two to compute how long a message sat in the receive queue.
756    /// It is meaningless on a message you allocated yourself.
757    #[inline]
758    pub fn time_received(&self) -> SteamNetworkingMicroseconds {
759        unsafe { (*self.0).m_usecTimeReceived }
760    }
761
762    #[inline]
763    pub fn lane(&self) -> GnsLaneId {
764        unsafe { (*self.0).m_idxLane }
765    }
766
767    #[inline]
768    pub fn flags(&self) -> SendFlags {
769        SendFlags::from_bits_retain(unsafe { (*self.0).m_nFlags })
770    }
771
772    #[inline]
773    pub fn user_data(&self) -> u64 {
774        unsafe { (*self.0).m_nUserData as _ }
775    }
776
777    #[inline]
778    pub fn connection(&self) -> GnsConnection {
779        GnsConnection(unsafe { (*self.0).m_conn })
780    }
781
782    #[inline]
783    pub fn connection_user_data(&self) -> u64 {
784        unsafe { (*self.0).m_nConnUserData as _ }
785    }
786}
787
788impl GnsNetworkMessage<ToSend> {
789    #[inline]
790    fn new<P: Payload>(
791        ptr: *mut ISteamNetworkingMessage,
792        conn: GnsConnection,
793        flags: SendFlags,
794        payload: P,
795    ) -> Self {
796        let (data_ptr, len) = payload.into_raw();
797        unsafe {
798            (*ptr).m_pData = data_ptr as *mut c_void;
799            (*ptr).m_cbSize = len as i32;
800            (*ptr).m_pfnFreeData = Some(free_payload::<P>);
801        }
802        GnsNetworkMessage(ptr, PhantomData)
803            .set_flags(flags)
804            .set_connection(conn)
805    }
806
807    #[inline]
808    pub fn set_connection(self, GnsConnection(conn): GnsConnection) -> Self {
809        unsafe { (*self.0).m_conn = conn }
810        self
811    }
812
813    #[inline]
814    pub fn set_lane(self, lane: GnsLaneId) -> Self {
815        unsafe { (*self.0).m_idxLane = lane }
816        self
817    }
818
819    #[inline]
820    pub fn set_flags(self, flags: SendFlags) -> Self {
821        unsafe { (*self.0).m_nFlags = flags.bits() as _ }
822        self
823    }
824
825    #[inline]
826    pub fn set_user_data(self, userdata: u64) -> Self {
827        unsafe { (*self.0).m_nUserData = userdata as _ }
828        self
829    }
830}
831
832#[repr(transparent)]
833#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
834pub struct GnsConnection(HSteamNetConnection);
835
836impl GnsConnection {
837    /// Wraps a raw `HSteamNetConnection` handle.
838    ///
839    /// GameNetworkingSockets validates the handle when you use it. It rejects
840    /// any handle that does not match a live connection.
841    #[inline]
842    pub const fn from_raw(handle: HSteamNetConnection) -> Self {
843        Self(handle)
844    }
845
846    /// Returns `true` if this is not the invalid-connection value (`0`).
847    #[inline]
848    pub fn is_valid(self) -> bool {
849        self.0 != k_HSteamNetConnection_Invalid
850    }
851}
852
853#[derive(Default, Copy, Clone)]
854pub struct GnsConnectionInfo(SteamNetConnectionInfo_t);
855
856impl GnsConnectionInfo {
857    #[inline]
858    pub fn state(&self) -> ESteamNetworkingConnectionState {
859        self.0.m_eState
860    }
861
862    #[inline]
863    pub fn end_reason(&self) -> u32 {
864        self.0.m_eEndReason as u32
865    }
866
867    #[inline]
868    pub fn end_debug(&self) -> &str {
869        unsafe { CStr::from_ptr(self.0.m_szEndDebug.as_ptr()) }
870            .to_str()
871            .unwrap_or("")
872    }
873
874    #[inline]
875    pub fn remote_address(&self) -> IpAddr {
876        ip_from_steam_ip_addr(&self.0.m_addrRemote)
877    }
878
879    #[inline]
880    pub fn remote_port(&self) -> u16 {
881        self.0.m_addrRemote.m_port
882    }
883}
884
885#[derive(Debug, Default, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
886pub struct GnsConnectionRealTimeLaneStatus(SteamNetConnectionRealTimeLaneStatus_t);
887
888impl GnsConnectionRealTimeLaneStatus {
889    #[inline]
890    pub fn pending_bytes_unreliable(&self) -> u32 {
891        self.0.m_cbPendingUnreliable as _
892    }
893
894    #[inline]
895    pub fn pending_bytes_reliable(&self) -> u32 {
896        self.0.m_cbPendingReliable as _
897    }
898
899    #[inline]
900    pub fn bytes_sent_unacked_reliable(&self) -> u32 {
901        self.0.m_cbSentUnackedReliable as _
902    }
903
904    #[inline]
905    pub fn approximated_queue_time(&self) -> Duration {
906        Duration::from_micros(self.0.m_usecQueueTime as _)
907    }
908}
909
910#[derive(Default, Debug, Copy, Clone, PartialOrd, PartialEq)]
911pub struct GnsConnectionRealTimeStatus(SteamNetConnectionRealTimeStatus_t);
912
913impl GnsConnectionRealTimeStatus {
914    #[inline]
915    pub fn state(&self) -> ESteamNetworkingConnectionState {
916        self.0.m_eState
917    }
918
919    #[inline]
920    pub fn ping(&self) -> u32 {
921        self.0.m_nPing as _
922    }
923
924    #[inline]
925    pub fn quality_local(&self) -> f32 {
926        self.0.m_flConnectionQualityLocal
927    }
928
929    #[inline]
930    pub fn quality_remote(&self) -> f32 {
931        self.0.m_flConnectionQualityRemote
932    }
933
934    #[inline]
935    pub fn out_packets_per_sec(&self) -> f32 {
936        self.0.m_flOutPacketsPerSec
937    }
938
939    #[inline]
940    pub fn out_bytes_per_sec(&self) -> f32 {
941        self.0.m_flOutBytesPerSec
942    }
943
944    #[inline]
945    pub fn in_packets_per_sec(&self) -> f32 {
946        self.0.m_flInPacketsPerSec
947    }
948
949    #[inline]
950    pub fn in_bytes_per_sec(&self) -> f32 {
951        self.0.m_flInBytesPerSec
952    }
953
954    #[inline]
955    pub fn send_rate_bytes_per_sec(&self) -> u32 {
956        self.0.m_nSendRateBytesPerSecond as _
957    }
958
959    #[inline]
960    pub fn pending_bytes_unreliable(&self) -> u32 {
961        self.0.m_cbPendingUnreliable as _
962    }
963
964    #[inline]
965    pub fn pending_bytes_reliable(&self) -> u32 {
966        self.0.m_cbPendingReliable as _
967    }
968
969    #[inline]
970    pub fn bytes_sent_unacked_reliable(&self) -> u32 {
971        self.0.m_cbSentUnackedReliable as _
972    }
973
974    #[inline]
975    pub fn approximated_queue_time(&self) -> Duration {
976        Duration::from_micros(self.0.m_usecQueueTime as _)
977    }
978
979    /// Returns the highest packet jitter seen since the last time you read this
980    /// value. Reading it clears the high water mark.
981    ///
982    /// Returns `None` if no jitter data is available, which happens when the
983    /// underlying value is negative or the connection type does not measure
984    /// jitter.
985    #[inline]
986    pub fn max_jitter_usec(&self) -> Option<i32> {
987        let val = self.0.m_usecMaxJitter;
988        if val < 0 {
989            None
990        } else {
991            Some(val)
992        }
993    }
994}
995
996#[derive(Default, Copy, Clone)]
997pub struct GnsConnectionEvent(SteamNetConnectionStatusChangedCallback_t);
998
999impl GnsConnectionEvent {
1000    #[inline]
1001    pub fn old_state(&self) -> ESteamNetworkingConnectionState {
1002        self.0.m_eOldState
1003    }
1004
1005    #[inline]
1006    pub fn connection(&self) -> GnsConnection {
1007        GnsConnection(self.0.m_hConn)
1008    }
1009
1010    #[inline]
1011    pub fn info(&self) -> GnsConnectionInfo {
1012        GnsConnectionInfo(self.0.m_info)
1013    }
1014}
1015
1016/// A network socket, and the main type of this library.
1017///
1018/// Use [`GnsSocket::connect`] to create a client socket and
1019/// [`GnsSocket::listen`] to create a server socket. Every operation on a socket
1020/// is safe.
1021///
1022/// Dropping a socket frees everything that belongs to it. It does not free the
1023/// [`GnsGlobal`] instance.
1024pub struct GnsSocket<S> {
1025    global: &'static GnsGlobal,
1026    state: S,
1027}
1028
1029impl<S> GnsSocket<S>
1030where
1031    S: IsReady,
1032{
1033    /// Returns the status of a connection and of its lanes.
1034    ///
1035    /// Configure the lanes with [`Self::configure_connection_lanes`] before you
1036    /// call this.
1037    pub fn get_connection_real_time_status(
1038        &self,
1039        GnsConnection(conn): GnsConnection,
1040        nb_of_lanes: u32,
1041    ) -> GnsResult<(
1042        GnsConnectionRealTimeStatus,
1043        Vec<GnsConnectionRealTimeLaneStatus>,
1044    )> {
1045        let mut lanes: Vec<GnsConnectionRealTimeLaneStatus> =
1046            vec![Default::default(); nb_of_lanes as _];
1047        let mut status: GnsConnectionRealTimeStatus = Default::default();
1048        check(unsafe {
1049            SteamAPI_ISteamNetworkingSockets_GetConnectionRealTimeStatus(
1050                get_interface(),
1051                conn,
1052                &mut status as *mut GnsConnectionRealTimeStatus
1053                    as *mut SteamNetConnectionRealTimeStatus_t,
1054                nb_of_lanes as _,
1055                lanes.as_mut_ptr() as *mut SteamNetConnectionRealTimeLaneStatus_t,
1056            )
1057        })?;
1058        Ok((status, lanes))
1059    }
1060
1061    pub fn get_connection_info(
1062        &self,
1063        GnsConnection(conn): GnsConnection,
1064    ) -> Option<GnsConnectionInfo> {
1065        let mut info: SteamNetConnectionInfo_t = Default::default();
1066        if unsafe {
1067            SteamAPI_ISteamNetworkingSockets_GetConnectionInfo(get_interface(), conn, &mut info)
1068        } {
1069            Some(GnsConnectionInfo(info))
1070        } else {
1071            None
1072        }
1073    }
1074
1075    /// Returns a verbose human-readable description of the state of a
1076    /// connection, intended for diagnostics and debug dumps.
1077    ///
1078    /// The format is subject to change between GameNetworkingSockets versions,
1079    /// so do not parse it. Returns `None` if the connection handle is invalid.
1080    pub fn get_detailed_connection_status(
1081        &self,
1082        GnsConnection(conn): GnsConnection,
1083    ) -> Option<String> {
1084        let mut buf = vec![0u8; 2048];
1085        loop {
1086            let result = unsafe {
1087                SteamAPI_ISteamNetworkingSockets_GetDetailedConnectionStatus(
1088                    get_interface(),
1089                    conn,
1090                    buf.as_mut_ptr() as *mut std::ffi::c_char,
1091                    buf.len() as _,
1092                )
1093            };
1094            if result < 0 {
1095                return None;
1096            }
1097            if result == 0 {
1098                let text = CStr::from_bytes_until_nul(&buf).ok()?;
1099                return Some(text.to_string_lossy().into_owned());
1100            }
1101            // The buffer was too small and `result` is the required size.
1102            buf.resize(result as usize, 0);
1103        }
1104    }
1105
1106    /// Returns the debug name of a connection, previously set with
1107    /// [`Self::set_connection_name`].
1108    ///
1109    /// Returns `None` if the connection handle is invalid.
1110    pub fn get_connection_name(&self, GnsConnection(conn): GnsConnection) -> Option<String> {
1111        let mut buf = [0u8; 256];
1112        if unsafe {
1113            SteamAPI_ISteamNetworkingSockets_GetConnectionName(
1114                get_interface(),
1115                conn,
1116                buf.as_mut_ptr() as *mut std::ffi::c_char,
1117                buf.len() as _,
1118            )
1119        } {
1120            let name = CStr::from_bytes_until_nul(&buf).ok()?;
1121            Some(name.to_string_lossy().into_owned())
1122        } else {
1123            None
1124        }
1125    }
1126
1127    /// Sets the debug name of a connection.
1128    ///
1129    /// The name shows up in diagnostics such as
1130    /// [`Self::get_detailed_connection_status`] and the debug output, which
1131    /// makes multi-connection logs much easier to read.
1132    ///
1133    /// # Errors
1134    /// Returns [`GnsError::Config`] if `name` contains an interior NUL byte.
1135    pub fn set_connection_name(
1136        &self,
1137        GnsConnection(conn): GnsConnection,
1138        name: &str,
1139    ) -> GnsResult<()> {
1140        let c = CString::new(name).map_err(|_| GnsError::Config("interior NUL"))?;
1141        unsafe {
1142            SteamAPI_ISteamNetworkingSockets_SetConnectionName(get_interface(), conn, c.as_ptr());
1143        }
1144        Ok(())
1145    }
1146
1147    pub fn flush_messages_on_connection(
1148        &self,
1149        GnsConnection(conn): GnsConnection,
1150    ) -> GnsResult<()> {
1151        check(unsafe {
1152            SteamAPI_ISteamNetworkingSockets_FlushMessagesOnConnection(get_interface(), conn)
1153        })
1154    }
1155
1156    /// Closes a connection.
1157    ///
1158    /// The wrapper forwards `debug` to the peer when you pass `Some`. Pass
1159    /// `None` to send no diagnostic string and avoid allocating.
1160    ///
1161    /// # Errors
1162    /// Returns [`GnsError::Close`] if the connection handle is invalid, for
1163    /// example because the connection is already closed.
1164    pub fn close_connection(
1165        &self,
1166        GnsConnection(conn): GnsConnection,
1167        reason: u32,
1168        debug: Option<&CStr>,
1169        linger: bool,
1170    ) -> GnsResult<()> {
1171        let debug_ptr = debug.map(|d| d.as_ptr()).unwrap_or(core::ptr::null());
1172        if unsafe {
1173            SteamAPI_ISteamNetworkingSockets_CloseConnection(
1174                get_interface(),
1175                conn,
1176                reason as _,
1177                debug_ptr,
1178                linger,
1179            )
1180        } {
1181            Ok(())
1182        } else {
1183            Err(GnsError::Close)
1184        }
1185    }
1186
1187    /// Receives up to `K` messages and returns an iterator over the ones that
1188    /// were available.
1189    ///
1190    /// Each message is yielded by value, so you can keep it, forward it, or let
1191    /// it drop, which releases it. Any message left in the iterator is released
1192    /// when the iterator is dropped.
1193    ///
1194    /// The `K`-slot pointer buffer lives inline in the returned iterator, so
1195    /// this call allocates nothing and copies no payload. Use
1196    /// [`receive_messages_into`](Self::receive_messages_into) to reuse one
1197    /// buffer across calls and avoid moving the inline array.
1198    ///
1199    /// # Errors
1200    /// Returns [`GnsError::Receive`] if the connection or poll group handle is
1201    /// invalid.
1202    pub fn receive_messages<const K: usize>(&self) -> GnsResult<ReceivedMessages<K>> {
1203        let mut slots: [MessageSlot; K] = [const { MessageSlot::uninit() }; K];
1204        let len = self.state.receive(&mut slots)?;
1205        Ok(ReceivedMessages {
1206            slots,
1207            cursor: SlotCursor { len, pos: 0 },
1208        })
1209    }
1210
1211    /// Receives up to `buffer.len()` messages into a buffer you own, and
1212    /// returns an iterator over the ones that were available.
1213    ///
1214    /// This is the variant of [`receive_messages`](Self::receive_messages) that
1215    /// neither allocates nor moves the buffer. GameNetworkingSockets fills
1216    /// `buffer` in place and the returned iterator borrows it, so reusing one
1217    /// buffer across a polling loop costs nothing per call.
1218    ///
1219    /// # Errors
1220    /// Returns [`GnsError::Receive`] if the connection or poll group handle is
1221    /// invalid.
1222    pub fn receive_messages_into<'a>(
1223        &self,
1224        buffer: &'a mut [MessageSlot],
1225    ) -> GnsResult<ReceivedMessagesInto<'a>> {
1226        let len = self.state.receive(buffer)?;
1227        Ok(ReceivedMessagesInto {
1228            slots: buffer,
1229            cursor: SlotCursor { len, pos: 0 },
1230        })
1231    }
1232
1233    /// Returns an iterator that drains the pending connection events.
1234    ///
1235    /// Unlike [`receive_messages`](Self::receive_messages), you supply no
1236    /// buffer. Events arrive on an internal lock-free queue that the
1237    /// connection-status callback fills, and this call pops from that queue.
1238    pub fn receive_events(&self) -> impl Iterator<Item = GnsConnectionEvent> + '_ {
1239        core::iter::from_fn(|| self.state.queue().pop())
1240    }
1241
1242    pub fn configure_connection_lanes(
1243        &self,
1244        GnsConnection(connection): GnsConnection,
1245        lanes: &[GnsLane],
1246    ) -> GnsResult<()> {
1247        let (priorities, weights): (Vec<i32>, Vec<u16>) =
1248            lanes.iter().map(|l| (l.priority, l.weight)).unzip();
1249        check(unsafe {
1250            SteamAPI_ISteamNetworkingSockets_ConfigureConnectionLanes(
1251                get_interface(),
1252                connection,
1253                lanes.len() as _,
1254                priorities.as_ptr(),
1255                weights.as_ptr(),
1256            )
1257        })
1258    }
1259
1260    /// Sends a single message to its target connection.
1261    ///
1262    /// This is a convenience wrapper over
1263    /// [`send_messages`](Self::send_messages) for the common one-message case.
1264    pub fn send_message(&self, message: GnsNetworkMessage<ToSend>) -> GnsResult<GnsMessageNumber> {
1265        match self.send_messages(core::iter::once(message)).pop() {
1266            Some(SendOutcome::Sent(number)) => Ok(number),
1267            Some(SendOutcome::Failed(result, _)) => Err(GnsError::Api(result)),
1268            // A single message is never `Skipped`, because that only happens
1269            // to a message queued behind an earlier failure on the same
1270            // connection. `send_messages` also returns one outcome per input.
1271            _ => Err(GnsError::Api(EResult::k_EResultFail)),
1272        }
1273    }
1274
1275    /// Sends each message to its target connection.
1276    ///
1277    /// The returned `Vec` holds one [`SendOutcome`] per input message, in the
1278    /// same order.
1279    pub fn send_messages(
1280        &self,
1281        messages: impl IntoIterator<Item = GnsNetworkMessage<ToSend>>,
1282    ) -> Vec<SendOutcome> {
1283        // Pass `bDeleteFailedMessages = false` so that the C library consumes
1284        // the messages it sends and leaves the failed and skipped ones for the
1285        // wrapper to wrap again. `ManuallyDrop` holds off the Rust destructor
1286        // across the FFI call.
1287        let mut raw: Vec<*mut ISteamNetworkingMessage> = messages
1288            .into_iter()
1289            .map(|message| {
1290                let message = core::mem::ManuallyDrop::new(message);
1291                message.0
1292            })
1293            .collect();
1294        let mut result = vec![0i64; raw.len()];
1295        unsafe {
1296            SteamAPI_ISteamNetworkingSockets_SendMessages(
1297                get_interface(),
1298                raw.len() as _,
1299                raw.as_mut_ptr(),
1300                result.as_mut_ptr(),
1301                false,
1302            );
1303        }
1304        result
1305            .into_iter()
1306            .zip(raw)
1307            .map(|(value, ptr)| {
1308                if value > 0 {
1309                    SendOutcome::Sent(value as _)
1310                } else if value < 0 {
1311                    // Sound because gns-sys pins GameNetworkingSockets as a
1312                    // submodule, so the generated `EResult` covers every value
1313                    // the library produces.
1314                    let result = unsafe { core::mem::transmute::<u32, EResult>((-value) as u32) };
1315                    SendOutcome::Failed(result, GnsNetworkMessage(ptr, PhantomData))
1316                } else {
1317                    SendOutcome::Skipped(GnsNetworkMessage(ptr, PhantomData))
1318                }
1319            })
1320            .collect()
1321    }
1322}
1323
1324impl GnsSocket<IsCreated> {
1325    /// The C callback that GameNetworkingSockets invokes on a connection-state
1326    /// change.
1327    ///
1328    /// The wrapper stores the queue ID in the connection user data, which is
1329    /// how this callback finds the right queue in [`GnsGlobal`].
1330    unsafe extern "C" fn on_connection_state_changed(
1331        info: &mut SteamNetConnectionStatusChangedCallback_t,
1332    ) {
1333        let gns_global = GnsGlobal::get()
1334            // Reaching this point at all means GnsGlobal is initialized.
1335            .expect("GnsGlobal should be initialized");
1336
1337        let queue_id = info.m_info.m_nUserData as _;
1338        // Fast path: take the read lock, look up the queue, and push if the
1339        // weak reference still upgrades.
1340        let needs_purge = {
1341            let queues = gns_global.event_queues.read().unwrap();
1342            match queues.get(&queue_id).and_then(Weak::upgrade) {
1343                Some(queue) => {
1344                    queue.push(GnsConnectionEvent(*info));
1345                    false
1346                }
1347                None => queues.contains_key(&queue_id),
1348            }
1349        };
1350        // Slow path: the socket was dropped while this callback ran, so the
1351        // entry is still in the map but the queue is gone. Take the write lock
1352        // to remove it. Queue IDs are never reused, so removing a key that
1353        // another thread already removed does no harm.
1354        if needs_purge {
1355            gns_global.event_queues.write().unwrap().remove(&queue_id);
1356        }
1357    }
1358
1359    /// Creates a socket in the [`IsCreated`] state.
1360    #[inline]
1361    pub fn new(global: &'static GnsGlobal) -> Self {
1362        GnsSocket {
1363            global,
1364            state: IsCreated,
1365        }
1366    }
1367
1368    fn setup_common(
1369        address: IpAddr,
1370        port: u16,
1371        queue_id: int64,
1372    ) -> (SteamNetworkingIPAddr, [SteamNetworkingConfigValue_t; 2]) {
1373        let addr = SteamNetworkingIPAddr {
1374            __bindgen_anon_1: match address {
1375                IpAddr::V4(address) => SteamNetworkingIPAddr__bindgen_ty_2 {
1376                    m_ipv4: SteamNetworkingIPAddr_IPv4MappedAddress {
1377                        m_8zeros: 0,
1378                        m_0000: 0,
1379                        m_ffff: 0xffff,
1380                        m_ip: address.octets(),
1381                    },
1382                },
1383                IpAddr::V6(address) => SteamNetworkingIPAddr__bindgen_ty_2 {
1384                    m_ipv6: address.octets(),
1385                },
1386            },
1387            m_port: port,
1388        };
1389        let options = [SteamNetworkingConfigValue_t {
1390            m_eDataType: ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Ptr,
1391            m_eValue: ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_Callback_ConnectionStatusChanged,
1392            m_val: SteamNetworkingConfigValue_t__bindgen_ty_1 {
1393              m_ptr: Self::on_connection_state_changed as *const fn(&SteamNetConnectionStatusChangedCallback_t) as *mut c_void
1394            }
1395          }, SteamNetworkingConfigValue_t {
1396            m_eDataType: ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Int64,
1397            m_eValue: ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_ConnectionUserData,
1398            m_val: SteamNetworkingConfigValue_t__bindgen_ty_1 {
1399              m_int64: queue_id
1400            }
1401        }];
1402        (addr, options)
1403    }
1404
1405    /// Listens for incoming connections.
1406    ///
1407    /// This moves the socket from [`IsCreated`] to [`IsServer`], which gives
1408    /// you the server operations.
1409    pub fn listen(self, address: IpAddr, port: u16) -> GnsResult<GnsSocket<IsServer>> {
1410        let (queue_id, queue) = self.global.create_queue();
1411        let (addr, options) = Self::setup_common(address, port, queue_id);
1412        let listen_socket = unsafe {
1413            SteamAPI_ISteamNetworkingSockets_CreateListenSocketIP(
1414                get_interface(),
1415                &addr,
1416                options.len() as _,
1417                options.as_ptr(),
1418            )
1419        };
1420        if listen_socket == k_HSteamListenSocket_Invalid {
1421            Err(GnsError::Listen)
1422        } else {
1423            let poll_group =
1424                unsafe { SteamAPI_ISteamNetworkingSockets_CreatePollGroup(get_interface()) };
1425            if poll_group == k_HSteamNetPollGroup_Invalid {
1426                Err(GnsError::Listen)
1427            } else {
1428                Ok(GnsSocket {
1429                    global: self.global,
1430                    state: IsServer {
1431                        queue,
1432                        queue_id,
1433                        global: self.global,
1434                        listen_socket: GnsListenSocket(listen_socket),
1435                        poll_group: GnsPollGroup(poll_group),
1436                    },
1437                })
1438            }
1439        }
1440    }
1441
1442    /// Connects to a remote host.
1443    ///
1444    /// This moves the socket from [`IsCreated`] to [`IsClient`], which gives
1445    /// you the client operations.
1446    pub fn connect(self, address: IpAddr, port: u16) -> GnsResult<GnsSocket<IsClient>> {
1447        let (queue_id, queue) = self.global.create_queue();
1448        let (addr, options) = Self::setup_common(address, port, queue_id);
1449        let connection = unsafe {
1450            SteamAPI_ISteamNetworkingSockets_ConnectByIPAddress(
1451                get_interface(),
1452                &addr,
1453                options.len() as _,
1454                options.as_ptr(),
1455            )
1456        };
1457        if connection == k_HSteamNetConnection_Invalid {
1458            Err(GnsError::Connect)
1459        } else {
1460            Ok(GnsSocket {
1461                global: self.global,
1462                state: IsClient {
1463                    queue,
1464                    queue_id,
1465                    global: self.global,
1466                    connection: GnsConnection(connection),
1467                },
1468            })
1469        }
1470    }
1471
1472    /// Creates a pair of connections that talk to each other, mainly for
1473    /// tests and loopback communication between parts of one process.
1474    ///
1475    /// With `use_network_loopback` set, the traffic goes through the local
1476    /// network stack over `127.0.0.1`. Without it, the payloads take an
1477    /// internal shortcut. See `ISteamNetworkingSockets::CreateSocketPair` for
1478    /// the trade-offs.
1479    ///
1480    /// Both sockets come back in the [`IsClient`] state and already connected.
1481    /// The connections are created before the wrapper can attach its
1482    /// connection-state callback, so the initial transition to the connected
1483    /// state never shows up in [`GnsSocket::receive_events`]. Later events,
1484    /// such as the peer closing the connection, are delivered normally.
1485    pub fn socket_pair(
1486        self,
1487        use_network_loopback: bool,
1488    ) -> GnsResult<(GnsSocket<IsClient>, GnsSocket<IsClient>)> {
1489        let (queue_id_a, queue_a) = self.global.create_queue();
1490        let (queue_id_b, queue_b) = self.global.create_queue();
1491        let mut conn_a = k_HSteamNetConnection_Invalid;
1492        let mut conn_b = k_HSteamNetConnection_Invalid;
1493        let ok = unsafe {
1494            SteamAPI_ISteamNetworkingSockets_CreateSocketPair(
1495                get_interface(),
1496                &mut conn_a,
1497                &mut conn_b,
1498                use_network_loopback,
1499                core::ptr::null(),
1500                core::ptr::null(),
1501            )
1502        };
1503        if !ok {
1504            let mut queues = self.global.event_queues.write().unwrap();
1505            queues.remove(&queue_id_a);
1506            queues.remove(&queue_id_b);
1507            return Err(GnsError::SocketPair);
1508        }
1509        // Build the client states first so that their `Drop` implementations
1510        // close the connections and unregister the queues on any later error.
1511        let make_client = |connection, queue, queue_id| GnsSocket {
1512            global: self.global,
1513            state: IsClient {
1514                queue,
1515                queue_id,
1516                global: self.global,
1517                connection: GnsConnection(connection),
1518            },
1519        };
1520        let a = make_client(conn_a, queue_a, queue_id_a);
1521        let b = make_client(conn_b, queue_b, queue_id_b);
1522        // `CreateSocketPair` accepts no config options, so install the same
1523        // callback and queue ID that `setup_common` passes at creation time.
1524        for (conn, queue_id) in [(conn_a, queue_id_a), (conn_b, queue_id_b)] {
1525            unsafe {
1526                SteamAPI_ISteamNetworkingSockets_SetConnectionUserData(
1527                    get_interface(),
1528                    conn,
1529                    queue_id,
1530                );
1531            }
1532            self.global.utils().set_config_value_scoped(
1533                ESteamNetworkingConfigValue::k_ESteamNetworkingConfig_Callback_ConnectionStatusChanged,
1534                ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Connection,
1535                conn as isize,
1536                GnsConfig::Ptr(Self::on_connection_state_changed as *const fn(&SteamNetConnectionStatusChangedCallback_t) as *mut c_void),
1537            )?;
1538        }
1539        Ok((a, b))
1540    }
1541}
1542
1543impl GnsSocket<IsServer> {
1544    /// Accepts an incoming connection. Only a socket in the [`IsServer`] state
1545    /// has this operation.
1546    pub fn accept(&self, connection: GnsConnection) -> GnsResult<()> {
1547        check(unsafe {
1548            SteamAPI_ISteamNetworkingSockets_AcceptConnection(get_interface(), connection.0)
1549        })?;
1550        if !unsafe {
1551            SteamAPI_ISteamNetworkingSockets_SetConnectionPollGroup(
1552                get_interface(),
1553                connection.0,
1554                self.state.poll_group.0,
1555            )
1556        } {
1557            // The poll group and the connection should both be valid here, so
1558            // this is not expected to happen.
1559            return Err(GnsError::Accept);
1560        }
1561        Ok(())
1562    }
1563
1564    /// Returns the address the listen socket is bound to.
1565    ///
1566    /// The address part is the unspecified address when the socket listens on
1567    /// every interface, which is what an all-zeros IP passed to
1568    /// [`GnsSocket::listen`] requests.
1569    pub fn get_listen_socket_address(&self) -> Option<(IpAddr, u16)> {
1570        let mut addr: SteamNetworkingIPAddr = unsafe { MaybeUninit::zeroed().assume_init() };
1571        if unsafe {
1572            SteamAPI_ISteamNetworkingSockets_GetListenSocketAddress(
1573                get_interface(),
1574                self.state.listen_socket.0,
1575                &mut addr,
1576            )
1577        } {
1578            Some((ip_from_steam_ip_addr(&addr), addr.m_port))
1579        } else {
1580            None
1581        }
1582    }
1583
1584    /// Sets a configuration value on the listen socket, for example a
1585    /// connection option that every accepted connection inherits as its
1586    /// default.
1587    pub fn set_listen_socket_config_value(
1588        &self,
1589        typ: ESteamNetworkingConfigValue,
1590        value: GnsConfig<'_>,
1591    ) -> GnsResult<()> {
1592        self.global.utils().set_config_value_scoped(
1593            typ,
1594            ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_ListenSocket,
1595            self.state.listen_socket.0 as isize,
1596            value,
1597        )
1598    }
1599
1600    /// Reads a configuration value back from the listen socket.
1601    pub fn get_listen_socket_config_value(
1602        &self,
1603        typ: ESteamNetworkingConfigValue,
1604    ) -> GnsResult<GnsConfigValue> {
1605        self.global.utils().get_config_value_scoped(
1606            typ,
1607            ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_ListenSocket,
1608            self.state.listen_socket.0 as isize,
1609        )
1610    }
1611}
1612
1613impl GnsSocket<IsClient> {
1614    /// Returns the socket connection. Only a socket in the [`IsClient`] state
1615    /// has this operation.
1616    #[inline]
1617    pub fn connection(&self) -> GnsConnection {
1618        self.state.connection
1619    }
1620}
1621
1622/// A configuration value for [`GnsUtils::set_global_config_value`] and
1623/// [`GnsUtils::set_connection_config_value`].
1624///
1625/// The enum is non-exhaustive so that variants for further
1626/// GameNetworkingSockets data types can be added. You can still construct
1627/// every existing variant.
1628#[non_exhaustive]
1629pub enum GnsConfig<'a> {
1630    Float(f32),
1631    Int32(i32),
1632    /// Allocates a `CString` so that the value ends in a NUL byte. Use
1633    /// [`GnsConfig::CStr`] to skip that allocation when you already hold a
1634    /// `CStr`.
1635    String(&'a str),
1636    /// A string variant that does not allocate, because `&CStr` already ends
1637    /// in a NUL byte.
1638    CStr(&'a CStr),
1639    Ptr(*mut c_void),
1640}
1641
1642/// A configuration value read back through [`GnsUtils::get_global_config_value`]
1643/// and friends.
1644///
1645/// Unlike [`GnsConfig`], which borrows what you pass in, this type owns its
1646/// data, and it distinguishes `Int64` because GameNetworkingSockets stores
1647/// some values (such as connection user data) as 64-bit integers.
1648///
1649/// The enum is non-exhaustive because it mirrors the GameNetworkingSockets
1650/// data-type enum, which can grow.
1651#[derive(Debug, Clone, PartialEq)]
1652#[non_exhaustive]
1653pub enum GnsConfigValue {
1654    Float(f32),
1655    Int32(i32),
1656    Int64(i64),
1657    String(String),
1658    Ptr(*mut c_void),
1659}
1660
1661pub struct GnsUtils(());
1662
1663type MsgPtr = *const ::std::os::raw::c_char;
1664
1665/// A debug callback that you supply.
1666///
1667/// It must be `Send + Sync` because GameNetworkingSockets invokes it from its
1668/// service thread, and it may capture state that your own threads share.
1669type DebugCallback = dyn Fn(ESteamNetworkingSocketsDebugOutputType, &str) + Send + Sync + 'static;
1670
1671/// Holds the callback that [`GnsUtils::enable_debug_output`] installs.
1672///
1673/// GameNetworkingSockets invokes it from its service thread, so this `OnceLock`
1674/// is the synchronization point.
1675static DEBUG_CB: OnceLock<Box<DebugCallback>> = OnceLock::new();
1676
1677unsafe extern "C" fn debug_trampoline(ty: ESteamNetworkingSocketsDebugOutputType, msg: MsgPtr) {
1678    if let Some(cb) = DEBUG_CB.get() {
1679        let s = unsafe { CStr::from_ptr(msg) }.to_str().unwrap_or("");
1680        cb(ty, s);
1681    }
1682}
1683
1684impl GnsUtils {
1685    /// Installs a debug callback.
1686    ///
1687    /// Only the first call takes effect. Later calls are ignored.
1688    ///
1689    /// GameNetworkingSockets runs the callback on its service thread, which is
1690    /// why the callback must be `Send + Sync + 'static`. The callback may
1691    /// capture state, because the wrapper stores it as a boxed closure. The
1692    /// `&str` is borrowed only for the duration of the call.
1693    pub fn enable_debug_output(
1694        &self,
1695        ty: ESteamNetworkingSocketsDebugOutputType,
1696        f: impl Fn(ESteamNetworkingSocketsDebugOutputType, &str) + Send + Sync + 'static,
1697    ) {
1698        let _ = DEBUG_CB.set(Box::new(f));
1699        unsafe {
1700            SteamAPI_ISteamNetworkingUtils_SetDebugOutputFunction(
1701                get_utils(),
1702                ty,
1703                Some(debug_trampoline),
1704            );
1705        }
1706    }
1707
1708    /// Allocates an outbound message and takes ownership of `payload`.
1709    ///
1710    /// The buffer stays alive until GameNetworkingSockets releases the message.
1711    /// At that point the wrapper rebuilds `P` with [`Payload::from_raw`] and
1712    /// drops it. Nothing is copied when the payload already owns heap memory.
1713    #[inline]
1714    pub fn allocate_message<P: Payload>(
1715        &self,
1716        conn: GnsConnection,
1717        flags: SendFlags,
1718        payload: P,
1719    ) -> GnsNetworkMessage<ToSend> {
1720        let message_ptr = unsafe { SteamAPI_ISteamNetworkingUtils_AllocateMessage(get_utils(), 0) };
1721        GnsNetworkMessage::new(message_ptr, conn, flags, payload)
1722    }
1723
1724    /// Sets a global configuration value, for example
1725    /// `k_ESteamNetworkingConfig_FakePacketLag_Send` to 1000 ms.
1726    pub fn set_global_config_value(
1727        &self,
1728        typ: ESteamNetworkingConfigValue,
1729        value: GnsConfig<'_>,
1730    ) -> GnsResult<()> {
1731        let result = match value {
1732            GnsConfig::Float(x) => unsafe {
1733                SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueFloat(get_utils(), typ, x)
1734            },
1735            GnsConfig::Int32(x) => unsafe {
1736                SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueInt32(get_utils(), typ, x)
1737            },
1738            GnsConfig::String(x) => {
1739                let c = CString::new(x).map_err(|_| GnsError::Config("interior NUL"))?;
1740                unsafe {
1741                    SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueString(
1742                        get_utils(),
1743                        typ,
1744                        c.as_ptr(),
1745                    )
1746                }
1747            }
1748            GnsConfig::CStr(x) => unsafe {
1749                SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValueString(
1750                    get_utils(),
1751                    typ,
1752                    x.as_ptr(),
1753                )
1754            },
1755            GnsConfig::Ptr(x) => unsafe {
1756                SteamAPI_ISteamNetworkingUtils_SetGlobalConfigValuePtr(get_utils(), typ, x)
1757            },
1758        };
1759        if result {
1760            Ok(())
1761        } else {
1762            Err(GnsError::Config("SetGlobalConfigValue rejected"))
1763        }
1764    }
1765
1766    /// Sets a configuration value on one connection, for example
1767    /// `k_ESteamNetworkingConfig_SendRateMin` on an accepted connection.
1768    pub fn set_connection_config_value(
1769        &self,
1770        conn: GnsConnection,
1771        typ: ESteamNetworkingConfigValue,
1772        value: GnsConfig<'_>,
1773    ) -> GnsResult<()> {
1774        let result = match value {
1775            GnsConfig::Float(x) => unsafe {
1776                SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueFloat(
1777                    get_utils(),
1778                    conn.0,
1779                    typ,
1780                    x,
1781                )
1782            },
1783            GnsConfig::Int32(x) => unsafe {
1784                SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueInt32(
1785                    get_utils(),
1786                    conn.0,
1787                    typ,
1788                    x,
1789                )
1790            },
1791            GnsConfig::String(x) => {
1792                let c = CString::new(x).map_err(|_| GnsError::Config("interior NUL"))?;
1793                unsafe {
1794                    SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueString(
1795                        get_utils(),
1796                        conn.0,
1797                        typ,
1798                        c.as_ptr(),
1799                    )
1800                }
1801            }
1802            GnsConfig::CStr(x) => unsafe {
1803                SteamAPI_ISteamNetworkingUtils_SetConnectionConfigValueString(
1804                    get_utils(),
1805                    conn.0,
1806                    typ,
1807                    x.as_ptr(),
1808                )
1809            },
1810            GnsConfig::Ptr(x) => {
1811                return self.set_config_value_scoped(
1812                    typ,
1813                    ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Connection,
1814                    conn.0 as isize,
1815                    GnsConfig::Ptr(x),
1816                )
1817            }
1818        };
1819        if result {
1820            Ok(())
1821        } else {
1822            Err(GnsError::Config("SetConnectionConfigValue rejected"))
1823        }
1824    }
1825
1826    /// Sets a configuration value on any scope through the generic
1827    /// `SetConfigValue` entry point.
1828    fn set_config_value_scoped(
1829        &self,
1830        typ: ESteamNetworkingConfigValue,
1831        scope: ESteamNetworkingConfigScope,
1832        scope_obj: isize,
1833        value: GnsConfig<'_>,
1834    ) -> GnsResult<()> {
1835        // Owner that must outlive the FFI call.
1836        let owned_string;
1837        // `SetConfigValue` reads a string value directly from `pArg`, but a
1838        // pointer value through one level of indirection: `pArg` must point to
1839        // the pointer.
1840        let (data_type, arg): (ESteamNetworkingConfigDataType, *const c_void) = match &value {
1841            GnsConfig::Float(x) => (
1842                ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Float,
1843                x as *const f32 as _,
1844            ),
1845            GnsConfig::Int32(x) => (
1846                ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Int32,
1847                x as *const i32 as _,
1848            ),
1849            GnsConfig::String(x) => {
1850                owned_string = CString::new(*x).map_err(|_| GnsError::Config("interior NUL"))?;
1851                (
1852                    ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_String,
1853                    owned_string.as_ptr() as _,
1854                )
1855            }
1856            GnsConfig::CStr(x) => (
1857                ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_String,
1858                x.as_ptr() as _,
1859            ),
1860            GnsConfig::Ptr(x) => (
1861                ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Ptr,
1862                x as *const *mut c_void as _,
1863            ),
1864        };
1865        if unsafe {
1866            SteamAPI_ISteamNetworkingUtils_SetConfigValue(
1867                get_utils(),
1868                typ,
1869                scope,
1870                scope_obj,
1871                data_type,
1872                arg,
1873            )
1874        } {
1875            Ok(())
1876        } else {
1877            Err(GnsError::Config("SetConfigValue rejected"))
1878        }
1879    }
1880
1881    /// Reads a configuration value from any scope.
1882    fn get_config_value_scoped(
1883        &self,
1884        typ: ESteamNetworkingConfigValue,
1885        scope: ESteamNetworkingConfigScope,
1886        scope_obj: isize,
1887    ) -> GnsResult<GnsConfigValue> {
1888        let mut data_type = ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Int32;
1889        let mut buf = vec![0u8; 64];
1890        let mut len = buf.len();
1891        loop {
1892            let result = unsafe {
1893                SteamAPI_ISteamNetworkingUtils_GetConfigValue(
1894                    get_utils(),
1895                    typ,
1896                    scope,
1897                    scope_obj,
1898                    &mut data_type,
1899                    buf.as_mut_ptr() as *mut c_void,
1900                    &mut len,
1901                )
1902            };
1903            match result {
1904                ESteamNetworkingGetConfigValueResult::k_ESteamNetworkingGetConfigValue_OK
1905                | ESteamNetworkingGetConfigValueResult::k_ESteamNetworkingGetConfigValue_OKInherited => {
1906                    break
1907                }
1908                ESteamNetworkingGetConfigValueResult::k_ESteamNetworkingGetConfigValue_BufferTooSmall => {
1909                    // `len` now holds the required size.
1910                    buf.resize(len, 0);
1911                }
1912                ESteamNetworkingGetConfigValueResult::k_ESteamNetworkingGetConfigValue_BadValue => {
1913                    return Err(GnsError::Config("unknown config value"))
1914                }
1915                ESteamNetworkingGetConfigValueResult::k_ESteamNetworkingGetConfigValue_BadScopeObj => {
1916                    return Err(GnsError::Config("bad scope object"))
1917                }
1918                _ => return Err(GnsError::Config("GetConfigValue failed")),
1919            }
1920        }
1921        // `buf` is a byte buffer, so read the typed values unaligned.
1922        let value = match data_type {
1923            ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Int32 => {
1924                GnsConfigValue::Int32(unsafe { (buf.as_ptr() as *const i32).read_unaligned() })
1925            }
1926            ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Int64 => {
1927                GnsConfigValue::Int64(unsafe { (buf.as_ptr() as *const i64).read_unaligned() })
1928            }
1929            ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Float => {
1930                GnsConfigValue::Float(unsafe { (buf.as_ptr() as *const f32).read_unaligned() })
1931            }
1932            ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_String => {
1933                let s = CStr::from_bytes_until_nul(&buf)
1934                    .map_err(|_| GnsError::Config("string value missing NUL"))?;
1935                GnsConfigValue::String(s.to_string_lossy().into_owned())
1936            }
1937            ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Ptr => {
1938                GnsConfigValue::Ptr(unsafe {
1939                    (buf.as_ptr() as *const *mut c_void).read_unaligned()
1940                })
1941            }
1942            _ => return Err(GnsError::Config("unknown config data type")),
1943        };
1944        Ok(value)
1945    }
1946
1947    /// Reads a global configuration value back, the counterpart of
1948    /// [`Self::set_global_config_value`].
1949    #[inline]
1950    pub fn get_global_config_value(
1951        &self,
1952        typ: ESteamNetworkingConfigValue,
1953    ) -> GnsResult<GnsConfigValue> {
1954        self.get_config_value_scoped(
1955            typ,
1956            ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Global,
1957            0,
1958        )
1959    }
1960
1961    /// Reads a configuration value from one connection, the counterpart of
1962    /// [`Self::set_connection_config_value`].
1963    ///
1964    /// A value that was never set on the connection itself comes back from the
1965    /// enclosing scope, such as the listen socket or the global defaults.
1966    #[inline]
1967    pub fn get_connection_config_value(
1968        &self,
1969        conn: GnsConnection,
1970        typ: ESteamNetworkingConfigValue,
1971    ) -> GnsResult<GnsConfigValue> {
1972        self.get_config_value_scoped(
1973            typ,
1974            ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Connection,
1975            conn.0 as isize,
1976        )
1977    }
1978
1979    /// Returns the name, data type, and maximally specific scope of a
1980    /// configuration value, or `None` if the value is unknown.
1981    pub fn get_config_value_info(
1982        &self,
1983        typ: ESteamNetworkingConfigValue,
1984    ) -> Option<(
1985        &'static str,
1986        ESteamNetworkingConfigDataType,
1987        ESteamNetworkingConfigScope,
1988    )> {
1989        let mut data_type = ESteamNetworkingConfigDataType::k_ESteamNetworkingConfig_Int32;
1990        let mut scope = ESteamNetworkingConfigScope::k_ESteamNetworkingConfig_Global;
1991        let name = unsafe {
1992            SteamAPI_ISteamNetworkingUtils_GetConfigValueInfo(
1993                get_utils(),
1994                typ,
1995                &mut data_type,
1996                &mut scope,
1997            )
1998        };
1999        if name.is_null() {
2000            None
2001        } else {
2002            // The name is a static string inside GameNetworkingSockets.
2003            let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap_or("");
2004            Some((name, data_type, scope))
2005        }
2006    }
2007
2008    /// Returns the current local timestamp, in microseconds.
2009    ///
2010    /// The timebase starts at a value that will never be confused with an
2011    /// interval, and it matches [`GnsNetworkMessage::time_received`].
2012    #[inline]
2013    pub fn local_timestamp(&self) -> SteamNetworkingMicroseconds {
2014        unsafe { SteamAPI_ISteamNetworkingUtils_GetLocalTimestamp(get_utils()) }
2015    }
2016}