ctru/services/
uds.rs

1//! UDS (local networking) service.
2//!
3//! The UDS service is used to handle local networking, i.e. peer-to-peer networking used for local multiplayer.
4//! This module also covers some functionality used in Download Play (dlp); there is a specific module for DLP, but it can also be implemented manually using UDS.
5#![doc(alias = "network")]
6#![doc(alias = "dlplay")]
7
8use std::error::Error as StdError;
9use std::ffi::CString;
10use std::fmt::{Debug, Display};
11use std::mem::MaybeUninit;
12use std::ops::FromResidual;
13use std::ptr::null;
14use std::sync::Mutex;
15
16use crate::error::ResultCode;
17use crate::services::ServiceReference;
18
19use bitflags::bitflags;
20use macaddr::MacAddr6;
21
22bitflags! {
23    /// Flags used for sending packets to a network.
24    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
25    pub struct SendFlags: u8 {
26        /// Unknown function according to `libctru`.
27        const Default = ctru_sys::UDS_SENDFLAG_Default;
28        /// Broadcast the data frame even when sending to a non-broadcast address.
29        const Broadcast = ctru_sys::UDS_SENDFLAG_Broadcast;
30    }
31}
32
33/// Error enum for generic errors within the [`Uds`] service.
34#[non_exhaustive]
35#[derive(Debug)]
36pub enum Error {
37    /// The provided username was too long.
38    UsernameTooLong,
39    /// The provided username contained a NULL byte.
40    UsernameContainsNull(usize),
41    /// Not connected to a network.
42    NotConnected,
43    /// No context bound.
44    NoContext,
45    /// Cannot send data on a network as a spectator.
46    Spectator,
47    /// No network created.
48    NoNetwork,
49    /// The provided app data buffer was too large.
50    TooMuchAppData,
51    /// The provided node ID does not reference a specific node.
52    NotANode,
53    /// ctru-rs error
54    Lib(crate::Error),
55}
56
57impl From<crate::Error> for Error {
58    fn from(value: crate::Error) -> Self {
59        Error::Lib(value)
60    }
61}
62
63impl<T> FromResidual<crate::Error> for Result<T, Error> {
64    fn from_residual(residual: crate::Error) -> Self {
65        Err(residual.into())
66    }
67}
68
69impl From<std::ffi::NulError> for Error {
70    fn from(value: std::ffi::NulError) -> Self {
71        Error::UsernameContainsNull(value.nul_position())
72    }
73}
74
75impl Display for Error {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        write!(
78            f,
79            "{}",
80            match self {
81                Self::UsernameTooLong =>
82                    "provided username was too long (max 10 bytes, not code points)".into(),
83                Self::UsernameContainsNull(pos) =>
84                    format!("provided username contained a NULL byte at position {pos}"),
85                Self::NotConnected => "not connected to a network".into(),
86                Self::NoContext => "no context bound".into(),
87                Self::Spectator => "cannot send data on a network as a spectator".into(),
88                Self::NoNetwork => "not hosting a network".into(),
89                Self::TooMuchAppData => "provided too much app data (max 200 bytes)".into(),
90                Self::NotANode => "provided node ID was non-specific".into(),
91                Self::Lib(e) => format!("ctru-rs error: {e}"),
92            }
93        )
94    }
95}
96
97impl StdError for Error {}
98
99/// Possible types of connection to a network.
100#[doc(alias = "udsConnectionType")]
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102#[repr(u8)]
103pub enum ConnectionType {
104    /// A normal client. Can push packets to the network.
105    Client = ctru_sys::UDSCONTYPE_Client,
106    /// A spectator. Cannot push packets to the network,
107    /// but doesn't need the passphrase to join.
108    Spectator = ctru_sys::UDSCONTYPE_Spectator,
109}
110
111impl From<ConnectionType> for u8 {
112    fn from(value: ConnectionType) -> Self {
113        value as Self
114    }
115}
116
117impl TryFrom<u8> for ConnectionType {
118    type Error = ();
119
120    fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
121        match value {
122            ctru_sys::UDSCONTYPE_Client => Ok(Self::Client),
123            ctru_sys::UDSCONTYPE_Spectator => Ok(Self::Spectator),
124            _ => Err(()),
125        }
126    }
127}
128
129/// ID for a node on the network.
130#[doc(alias = "NetworkNodeID")]
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
132pub enum NodeID {
133    /// No node ID set (not connected to a network).
134    None,
135    /// A normal node on the network, counting from 1 (the host) to 16, inclusive.
136    Node(u8),
137    /// Broadcast to all nodes
138    Broadcast,
139}
140
141impl From<NodeID> for u16 {
142    fn from(value: NodeID) -> Self {
143        match value {
144            NodeID::None => 0,
145            NodeID::Node(node) => node as u16,
146            NodeID::Broadcast => ctru_sys::UDS_BROADCAST_NETWORKNODEID as u16,
147        }
148    }
149}
150
151impl TryFrom<u16> for NodeID {
152    type Error = ();
153
154    fn try_from(value: u16) -> std::result::Result<Self, Self::Error> {
155        match value as u32 {
156            0 => Ok(Self::None),
157            ctru_sys::UDS_HOST_NETWORKNODEID..=ctru_sys::UDS_MAXNODES => {
158                Ok(Self::Node(value as u8))
159            }
160            ctru_sys::UDS_BROADCAST_NETWORKNODEID => Ok(Self::Broadcast),
161            _ => Err(()),
162        }
163    }
164}
165
166/// Information about a network node.
167#[doc(alias = "udsNodeInfo")]
168#[derive(Copy, Clone)]
169pub struct NodeInfo(ctru_sys::udsNodeInfo);
170
171impl Debug for NodeInfo {
172    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173        write!(f, "NodeInfo(")?;
174
175        f.debug_struct("udsNodeInfo")
176            .field("uds_friendcodeseed", &self.uds_friendcodeseed())
177            .field("username", &self.username())
178            .field("flag", &self.flag())
179            .field("NetworkNodeID", &self.node_id())
180            .finish()?;
181
182        write!(f, ")")
183    }
184}
185
186impl From<ctru_sys::udsNodeInfo> for NodeInfo {
187    fn from(value: ctru_sys::udsNodeInfo) -> Self {
188        Self(value)
189    }
190}
191
192impl NodeInfo {
193    /// Friend code seed associated with this network node.
194    pub fn uds_friendcodeseed(&self) -> u64 {
195        self.0.uds_friendcodeseed
196    }
197
198    /// Username associated with this network node.
199    pub fn username(&self) -> String {
200        String::from_utf16_lossy(unsafe { &self.0.__bindgen_anon_1.__bindgen_anon_1.username })
201    }
202
203    /// Flag associated with this network node.
204    pub fn flag(&self) -> u8 {
205        unsafe { self.0.__bindgen_anon_1.__bindgen_anon_1.flag }
206    }
207
208    /// Node ID associated with this network node.
209    pub fn node_id(&self) -> NodeID {
210        self.0
211            .NetworkNodeID
212            .try_into()
213            .expect("UDS service should always provide a valid NetworkNodeID")
214    }
215}
216
217/// Information returned from scanning for networks.
218#[doc(alias = "udsNetworkScanInfo")]
219#[derive(Copy, Clone)]
220pub struct NetworkScanInfo(ctru_sys::udsNetworkScanInfo);
221
222impl Debug for NetworkScanInfo {
223    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224        write!(f, "NetworkScanInfo(")?;
225
226        f.debug_struct("udsNetworkScanInfo")
227            .field("datareply_entry", &self.datareply_entry())
228            .field("network", &self.network())
229            .field("nodes", &self.nodes())
230            .finish()?;
231
232        write!(f, ")")
233    }
234}
235
236impl From<ctru_sys::udsNetworkScanInfo> for NetworkScanInfo {
237    fn from(value: ctru_sys::udsNetworkScanInfo) -> Self {
238        Self(value)
239    }
240}
241
242impl NetworkScanInfo {
243    /// NWM output structure.
244    pub fn datareply_entry(&self) -> ctru_sys::nwmBeaconDataReplyEntry {
245        self.0.datareply_entry
246    }
247
248    /// Get a reference to the NWM output structure.
249    pub fn datareply_entry_ref(&self) -> &ctru_sys::nwmBeaconDataReplyEntry {
250        &self.0.datareply_entry
251    }
252
253    /// Get a mutable reference to the NWM output structure.
254    pub fn datareply_entry_mut(&mut self) -> &mut ctru_sys::nwmBeaconDataReplyEntry {
255        &mut self.0.datareply_entry
256    }
257
258    /// Information about the network.
259    pub fn network(&self) -> ctru_sys::udsNetworkStruct {
260        self.0.network
261    }
262
263    /// Get a reference to the information about the network.
264    pub fn network_ref(&self) -> &ctru_sys::udsNetworkStruct {
265        &self.0.network
266    }
267
268    /// Get a mutable reference to the information about the network.
269    pub fn network_mut(&mut self) -> &mut ctru_sys::udsNetworkStruct {
270        &mut self.0.network
271    }
272
273    /// All nodes on the network (first node is the server,
274    /// max 16, `None` means no node connected).
275    pub fn nodes(&self) -> [Option<NodeInfo>; 16] {
276        self.0.nodes.map(|n| {
277            if n.uds_friendcodeseed != 0 {
278                Some(n.into())
279            } else {
280                None
281            }
282        })
283    }
284}
285
286/// Possible raw connection status values.
287#[derive(Debug, Clone, Copy, PartialEq, Eq)]
288#[repr(u32)]
289#[non_exhaustive]
290pub enum ConnectionStatusInfo {
291    /// Not connected to any network.
292    Disconnected = 3,
293    /// Connected as a host.
294    Host = 6,
295    /// Connected as a client.
296    Client = 9,
297    /// Connected as a spectator.
298    Spectator = 10,
299    /// Unknown
300    Unknown = 11,
301}
302
303impl From<ConnectionStatusInfo> for u32 {
304    fn from(value: ConnectionStatusInfo) -> Self {
305        value as Self
306    }
307}
308
309impl TryFrom<u32> for ConnectionStatusInfo {
310    type Error = ();
311
312    fn try_from(value: u32) -> std::result::Result<Self, Self::Error> {
313        match value {
314            3 => Ok(Self::Disconnected),
315            6 => Ok(Self::Host),
316            9 => Ok(Self::Client),
317            10 => Ok(Self::Spectator),
318            11 => Ok(Self::Unknown),
319            _ => Err(()),
320        }
321    }
322}
323
324/// Status of the connection.
325#[doc(alias = "udsConnectionStatus")]
326#[derive(Clone, Copy)]
327pub struct ConnectionStatus(ctru_sys::udsConnectionStatus);
328
329impl Debug for ConnectionStatus {
330    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
331        write!(f, "ConnectionStatus(")?;
332
333        f.debug_struct("udsConnectionStatus")
334            .field("status", &self.status())
335            .field("cur_node_id", &self.cur_node_id())
336            .field("total_nodes", &self.total_nodes())
337            .field("max_nodes", &self.max_nodes())
338            .field("node_bitmask", &self.node_bitmask())
339            .finish()?;
340
341        write!(f, ")")
342    }
343}
344
345impl From<ctru_sys::udsConnectionStatus> for ConnectionStatus {
346    fn from(value: ctru_sys::udsConnectionStatus) -> Self {
347        Self(value)
348    }
349}
350
351impl ConnectionStatus {
352    /// Raw status information.
353    pub fn status(&self) -> Option<ConnectionStatusInfo> {
354        self.0.status.try_into().ok()
355    }
356
357    /// Network node ID for the current device.
358    pub fn cur_node_id(&self) -> NodeID {
359        self.0
360            .cur_NetworkNodeID
361            .try_into()
362            .expect("UDS service should always provide a valid NetworkNodeID")
363    }
364
365    /// Number of nodes connected to the network.
366    pub fn total_nodes(&self) -> u8 {
367        self.0.total_nodes
368    }
369
370    /// Maximum nodes allowed on this network.
371    pub fn max_nodes(&self) -> u8 {
372        self.0.max_nodes
373    }
374
375    /// Bitmask for which of the 16 possible nodes are connected
376    /// to this network; bit 0 is the server, bit 1 is the first
377    /// original client, etc.
378    pub fn node_bitmask(&self) -> u16 {
379        self.0.node_bitmask
380    }
381}
382
383/// Status of the service handle.
384#[derive(Debug, Clone, Copy, PartialEq, Eq)]
385pub enum ServiceStatus {
386    /// Not connected to or hosting a network.
387    Disconnected,
388    /// Connected to a network.
389    Client,
390    /// Hosting a network.
391    Server,
392}
393
394/// Handle to the UDS service.
395pub struct Uds {
396    _service_handler: ServiceReference,
397    context: Option<ctru_sys::udsBindContext>,
398    network: Option<ctru_sys::udsNetworkStruct>,
399    scan_buf: Box<[u8; Self::SCAN_BUF_SIZE]>,
400}
401
402static UDS_ACTIVE: Mutex<()> = Mutex::new(());
403
404impl Uds {
405    /// Size of one frame.
406    const RECV_FRAME_SIZE: usize = ctru_sys::UDS_DATAFRAME_MAXSIZE as usize;
407
408    /// Size of receive buffer; max frame size * 8.
409    const RECV_BUF_SIZE: u32 = ctru_sys::UDS_DEFAULT_RECVBUFSIZE;
410
411    /// Shared memory size; must be slightly larger
412    /// than `RECV_BUF_SIZE`.
413    const SHAREDMEM_SIZE: usize = 0x3000;
414
415    /// Buffer used while scanning for networks.
416    /// This value is taken from the devkitPRO example.
417    const SCAN_BUF_SIZE: usize = 0x4000;
418
419    /// The maximum number of nodes that can ever be connected
420    /// to a network (16). Can be further limited.
421    const MAX_NODES: u8 = ctru_sys::UDS_MAXNODES as u8;
422
423    /// The maximum amount of app data any server can provide.
424    /// Limited by the size of a struct in libctru.
425    const MAX_APPDATA_SIZE: usize = Self::size_of_call(|s: ctru_sys::udsNetworkStruct| s.appdata);
426
427    const fn size_of_call<T, U>(_: fn(T) -> U) -> usize {
428        std::mem::size_of::<U>()
429    }
430
431    /// Retrieve the current status of the service.
432    pub fn service_status(&self) -> ServiceStatus {
433        match (self.context, self.network) {
434            (None, None) => ServiceStatus::Disconnected,
435            (Some(_), None) => ServiceStatus::Client,
436            (Some(_), Some(_)) => ServiceStatus::Server,
437            _ => unreachable!(),
438        }
439    }
440
441    /// Initialise a new service handle.
442    /// No `new_with_buffer_size` function is provided, as there isn't really a
443    /// reason to use any size other than the default.
444    ///
445    /// The `username` parameter should be a max 10-byte (not 10 code point!) UTF-8 string, converted to UTF-16 internally.
446    /// Pass `None` to use the 3DS's configured username.
447    ///
448    /// # Errors
449    ///
450    /// This function will return an error if the [`Uds`] service is already being used,
451    /// or if the provided username is invalid (longer than 10 bytes or contains a NULL byte).
452    ///
453    /// # Example
454    ///
455    /// ```
456    /// # let _runner = test_runner::GdbRunner::default();
457    /// # use std::error::Error;
458    /// # fn main() -> Result<(), Box<dyn Error>> {
459    /// #
460    /// use ctru::services::uds::Uds;
461    ///
462    /// let uds = Uds::new(None)?;
463    /// #
464    /// # Ok(())
465    /// # }
466    /// ```
467    #[doc(alias = "udsInit")]
468    pub fn new(username: Option<&str>) -> Result<Self, Error> {
469        if let Some(n) = username
470            && n.len() > 10
471        {
472            return Err(Error::UsernameTooLong);
473        }
474
475        let cstr = username.map(CString::new).transpose()?;
476        let handler = ServiceReference::new(
477            &UDS_ACTIVE,
478            || {
479                let ptr = cstr.map(|c| c.as_ptr()).unwrap_or(null());
480
481                ResultCode(unsafe { ctru_sys::udsInit(Self::SHAREDMEM_SIZE, ptr) })?;
482
483                Ok(())
484            },
485            || unsafe {
486                ctru_sys::udsExit();
487            },
488        )?;
489
490        Ok(Self {
491            _service_handler: handler,
492            context: None,
493            network: None,
494            scan_buf: Box::new([0; Self::SCAN_BUF_SIZE]),
495        })
496    }
497
498    /// Scan the UDS service for all available beacons broadcasting with the given IDs.
499    ///
500    /// This function must be called to obtain network objects that can later be connected to.
501    ///
502    /// # Example
503    ///
504    /// ```
505    /// # let _runner = test_runner::GdbRunner::default();
506    /// # use std::error::Error;
507    /// # fn main() -> Result<(), Box<dyn Error>> {
508    /// #
509    /// use ctru::services::uds::Uds;
510    /// let mut uds = Uds::new(None)?;
511    ///
512    /// let networks = uds.scan(b"HBW\x10", None, None)?;
513    /// #
514    /// # Ok(())
515    /// # }
516    /// ```
517    #[doc(alias = "udsScanBeacons")]
518    pub fn scan(
519        &mut self,
520        comm_id: &[u8; 4],
521        additional_id: Option<u8>,
522        whitelist_macaddr: Option<MacAddr6>,
523    ) -> crate::Result<Vec<NetworkScanInfo>> {
524        self.scan_buf.fill(0);
525
526        let mut networks = MaybeUninit::uninit();
527        let mut total_networks = MaybeUninit::uninit();
528
529        ResultCode(unsafe {
530            ctru_sys::udsScanBeacons(
531                self.scan_buf.as_mut_ptr().cast(),
532                Self::SCAN_BUF_SIZE,
533                networks.as_mut_ptr(),
534                total_networks.as_mut_ptr(),
535                u32::from_be_bytes(*comm_id),
536                additional_id.unwrap_or(0),
537                whitelist_macaddr
538                    .map(|m| m.as_bytes().as_ptr())
539                    .unwrap_or_default(),
540                self.service_status() == ServiceStatus::Client,
541            )
542        })?;
543
544        let networks = unsafe { networks.assume_init() };
545        let total_networks = unsafe { total_networks.assume_init() };
546
547        let networks = if total_networks > 0 {
548            // Safety: `networks` is malloced in application memory with size = `total_networks`
549            unsafe { Vec::from_raw_parts(networks, total_networks, total_networks) }
550                .into_iter()
551                .map(NetworkScanInfo::from)
552                .collect()
553        } else {
554            vec![]
555        };
556
557        Ok(networks)
558    }
559
560    /// Retrieve app data for a network which the service is not connected to.
561    ///
562    /// # Example
563    ///
564    /// ```
565    /// # let _runner = test_runner::GdbRunner::default();
566    /// # use std::error::Error;
567    /// # fn main() -> Result<(), Box<dyn Error>> {
568    /// #
569    /// use ctru::services::uds::Uds;
570    /// let mut uds = Uds::new(None)?;
571    ///
572    /// let networks = uds.scan(b"HBW\x10", None, None)?;
573    /// let appdata = uds.network_appdata(&networks[0], None)?;
574    /// #
575    /// # Ok(())
576    /// # }
577    /// ```
578    #[doc(alias = "udsGetNetworkStructApplicationData")]
579    pub fn network_appdata(
580        &self,
581        network: &NetworkScanInfo,
582        max_size: Option<usize>,
583    ) -> crate::Result<Vec<u8>> {
584        let mut appdata_buffer = vec![
585            0u8;
586            max_size
587                .unwrap_or(Self::MAX_APPDATA_SIZE)
588                .min(Self::MAX_APPDATA_SIZE)
589        ];
590
591        let mut actual_size = MaybeUninit::uninit();
592
593        ResultCode(unsafe {
594            ctru_sys::udsGetNetworkStructApplicationData(
595                network.network_ref() as *const _,
596                appdata_buffer.as_mut_ptr().cast(),
597                appdata_buffer.len(),
598                actual_size.as_mut_ptr(),
599            )
600        })?;
601
602        let actual_size = unsafe { actual_size.assume_init() };
603
604        appdata_buffer.truncate(actual_size);
605        appdata_buffer.shrink_to_fit();
606
607        Ok(appdata_buffer)
608    }
609
610    /// Retrieve app data for the currently connected network.
611    ///
612    /// # Errors
613    ///
614    /// This function will return an error if the service is not connected to a network.
615    /// See [`Uds::connect_network()`] to connect to a network.
616    ///
617    /// # Example
618    ///
619    /// ```
620    /// # let _runner = test_runner::GdbRunner::default();
621    /// # use std::error::Error;
622    /// # fn main() -> Result<(), Box<dyn Error>> {
623    /// #
624    /// use ctru::services::uds::{ConnectionType, Uds};
625    /// let mut uds = Uds::new(None)?;
626    ///
627    /// let networks = uds.scan(b"HBW\x10", None, None)?;
628    /// uds.connect_network(&networks[0], b"udsdemo passphrase c186093cd2652741\0", ConnectionType::Client, 1)?;
629    /// let appdata = uds.appdata(None)?;
630    /// #
631    /// # Ok(())
632    /// # }
633    /// ```
634    #[doc(alias = "udsGetApplicationData")]
635    pub fn appdata(&self, max_size: Option<usize>) -> Result<Vec<u8>, Error> {
636        if self.service_status() == ServiceStatus::Disconnected {
637            return Err(Error::NotConnected);
638        }
639
640        let mut appdata_buffer = vec![
641            0u8;
642            max_size
643                .unwrap_or(Self::MAX_APPDATA_SIZE)
644                .min(Self::MAX_APPDATA_SIZE)
645        ];
646
647        let mut actual_size = MaybeUninit::uninit();
648
649        ResultCode(unsafe {
650            ctru_sys::udsGetApplicationData(
651                appdata_buffer.as_mut_ptr().cast(),
652                appdata_buffer.len(),
653                actual_size.as_mut_ptr(),
654            )
655        })?;
656
657        let actual_size = unsafe { actual_size.assume_init() };
658
659        appdata_buffer.truncate(actual_size);
660        appdata_buffer.shrink_to_fit();
661
662        Ok(appdata_buffer)
663    }
664
665    /// Connect to a network.
666    ///
667    /// # Example
668    ///
669    /// ```
670    /// # let _runner = test_runner::GdbRunner::default();
671    /// # use std::error::Error;
672    /// # fn main() -> Result<(), Box<dyn Error>> {
673    /// #
674    /// use ctru::services::uds::{ConnectionType, Uds};
675    /// let mut uds = Uds::new(None)?;
676    ///
677    /// let networks = uds.scan(b"HBW\x10", None, None)?;
678    /// uds.connect_network(&networks[0], b"udsdemo passphrase c186093cd2652741\0", ConnectionType::Client, 1)?;
679    /// #
680    /// # Ok(())
681    /// # }
682    /// ```
683    #[doc(alias = "udsConnectNetwork")]
684    pub fn connect_network(
685        &mut self,
686        network: &NetworkScanInfo,
687        passphrase: &[u8],
688        connection_type: ConnectionType,
689        channel: u8,
690    ) -> crate::Result<()> {
691        let mut context = MaybeUninit::uninit();
692
693        ResultCode(unsafe {
694            ctru_sys::udsConnectNetwork(
695                network.network_ref() as *const _,
696                passphrase.as_ptr().cast(),
697                passphrase.len(),
698                context.as_mut_ptr(),
699                NodeID::Broadcast.into(),
700                connection_type as u8,
701                channel,
702                Self::RECV_BUF_SIZE,
703            )
704        })?;
705
706        let context = unsafe { context.assume_init() };
707
708        self.context.replace(context);
709
710        Ok(())
711    }
712
713    /// Disconnect from a network.
714    ///
715    /// # Errors
716    ///
717    /// This function will return an error if the service is not connected to a network.
718    /// See [`Uds::connect_network()`] to connect to a network.
719    ///
720    /// # Example
721    ///
722    /// ```
723    /// # let _runner = test_runner::GdbRunner::default();
724    /// # use std::error::Error;
725    /// # fn main() -> Result<(), Box<dyn Error>> {
726    /// #
727    /// use ctru::services::uds::{ConnectionType, Uds};
728    /// let mut uds = Uds::new(None)?;
729    ///
730    /// let networks = uds.scan(b"HBW\x10", None, None)?;
731    /// uds.connect_network(&networks[0], b"udsdemo passphrase c186093cd2652741\0", ConnectionType::Client, 1)?;
732    /// uds.disconnect_network()?;
733    /// #
734    /// # Ok(())
735    /// # }
736    /// ```
737    #[doc(alias = "udsDisconnectNetwork")]
738    pub fn disconnect_network(&mut self) -> Result<(), Error> {
739        if self.service_status() != ServiceStatus::Client {
740            return Err(Error::NotConnected);
741        }
742
743        if self.context.is_some() {
744            self.unbind_context()?;
745        }
746
747        ResultCode(unsafe { ctru_sys::udsDisconnectNetwork() })?;
748
749        Ok(())
750    }
751
752    /// Unbind the connection context.
753    ///
754    /// Normally, there's no reason to call this function,
755    /// since [`Uds::disconnect_network()`] and [`Uds::destroy_network()`] both automatically unbind their contexts.
756    ///
757    /// # Errors
758    ///
759    /// This function will return an error if no context is currently bound (i.e. the service is neither connected to nor hosting a network).
760    /// See [`Uds::connect_network()`] to connect to a network or [`Uds::create_network()`] to create one.
761    ///
762    /// # Example
763    ///
764    /// ```
765    /// # let _runner = test_runner::GdbRunner::default();
766    /// # use std::error::Error;
767    /// # fn main() -> Result<(), Box<dyn Error>> {
768    /// #
769    /// use ctru::services::uds::{ConnectionType, Uds};
770    /// let mut uds = Uds::new(None)?;
771    ///
772    /// let networks = uds.scan(b"HBW\x10", None, None)?;
773    /// uds.connect_network(&networks[0], b"udsdemo passphrase c186093cd2652741\0", ConnectionType::Client, 1)?;
774    /// uds.unbind_context()?;
775    /// #
776    /// # Ok(())
777    /// # }
778    /// ```
779    #[doc(alias = "udsUnbind")]
780    pub fn unbind_context(&mut self) -> Result<(), Error> {
781        if let Some(mut ctx) = self.context {
782            ResultCode(unsafe { ctru_sys::udsUnbind(&mut ctx as *mut _) })?;
783        } else {
784            return Err(Error::NoContext);
785        }
786
787        self.context = None;
788
789        Ok(())
790    }
791
792    /// Returns the Wi-Fi channel currently in use.
793    ///
794    /// # Errors
795    ///
796    /// This function will return an error if the service is currently neither connected to nor hosting a network.
797    /// See [`Uds::connect_network()`] to connect to a network or [`Uds::create_network()`] to create one.
798    ///
799    /// # Example
800    ///
801    /// ```
802    /// # let _runner = test_runner::GdbRunner::default();
803    /// # use std::error::Error;
804    /// # fn main() -> Result<(), Box<dyn Error>> {
805    /// #
806    /// use ctru::services::uds::{ConnectionType, Uds};
807    /// let mut uds = Uds::new(None)?;
808    ///
809    /// let networks = uds.scan(b"HBW\x10", None, None)?;
810    /// uds.connect_network(&networks[0], b"udsdemo passphrase c186093cd2652741\0", ConnectionType::Client, 1)?;
811    /// let channel = uds.channel()?;
812    /// #
813    /// # Ok(())
814    /// # }
815    /// ```
816    #[doc(alias = "udsGetChannel")]
817    pub fn channel(&self) -> Result<u8, Error> {
818        if self.service_status() == ServiceStatus::Disconnected {
819            return Err(Error::NotConnected);
820        }
821
822        let mut channel = MaybeUninit::uninit();
823
824        ResultCode(unsafe { ctru_sys::udsGetChannel(channel.as_mut_ptr()) })?;
825
826        let channel = unsafe { channel.assume_init() };
827
828        Ok(channel)
829    }
830
831    /// Wait for a ConnectionStatus event to occur.
832    ///
833    /// If `next` is `true`, discard the current event (if any) and wait for the next one.
834    ///
835    /// If `wait` is `true`, block until an event is signalled, else return `false` if no event.
836    ///
837    /// Always returns `true`, unless `wait` is `false` and no event has been signalled.
838    ///
839    /// # Errors
840    ///
841    /// This function will return an error if the service is currently neither connected to nor hosting a network.
842    /// See [`Uds::connect_network()`] to connect to a network or [`Uds::create_network()`] to create one.
843    ///
844    /// # Example
845    ///
846    /// ```
847    /// # let _runner = test_runner::GdbRunner::default();
848    /// # use std::error::Error;
849    /// # fn main() -> Result<(), Box<dyn Error>> {
850    /// #
851    /// use ctru::services::uds::{ConnectionType, Uds};
852    /// let mut uds = Uds::new(None)?;
853    ///
854    /// let networks = uds.scan(b"HBW\x10", None, None)?;
855    /// uds.connect_network(&networks[0], b"udsdemo passphrase c186093cd2652741\0", ConnectionType::Client, 1)?;
856    /// if uds.wait_status_event(false, false)? {
857    ///     println!("Event signalled");
858    /// }
859    /// #
860    /// # Ok(())
861    /// # }
862    /// ```
863    #[doc(alias = "udsWaitConnectionStatusEvent")]
864    pub fn wait_status_event(&self, next: bool, wait: bool) -> Result<bool, Error> {
865        if self.service_status() == ServiceStatus::Disconnected {
866            return Err(Error::NotConnected);
867        }
868
869        Ok(unsafe { ctru_sys::udsWaitConnectionStatusEvent(next, wait) })
870    }
871
872    /// Returns the current [`ConnectionStatus`] struct.
873    ///
874    /// # Example
875    ///
876    /// ```
877    /// # let _runner = test_runner::GdbRunner::default();
878    /// # use std::error::Error;
879    /// # fn main() -> Result<(), Box<dyn Error>> {
880    /// #
881    /// use ctru::services::uds::{ConnectionType, Uds};
882    /// let mut uds = Uds::new(None)?;
883    ///
884    /// let networks = uds.scan(b"HBW\x10", None, None)?;
885    /// uds.connect_network(&networks[0], b"udsdemo passphrase c186093cd2652741\0", ConnectionType::Client, 1)?;
886    /// if uds.wait_status_event(false, false)? {
887    ///     println!("Connection status event signalled");
888    ///     let status = uds.connection_status()?;
889    ///     println!("Status: {status:#X?}");
890    /// }
891    /// #
892    /// # Ok(())
893    /// # }
894    /// ```
895    #[doc(alias = "udsGetConnectionStatus")]
896    pub fn connection_status(&self) -> crate::Result<ConnectionStatus> {
897        let mut status = MaybeUninit::uninit();
898
899        ResultCode(unsafe { ctru_sys::udsGetConnectionStatus(status.as_mut_ptr()) })?;
900
901        let status = unsafe { status.assume_init() };
902
903        Ok(status.into())
904    }
905
906    /// Send a packet to the network.
907    ///
908    /// TODO: max size?
909    ///
910    /// # Errors
911    ///
912    /// This function will return an error if the service is currently neither connected to nor hosting a network.
913    /// See [`Uds::connect_network()`] to connect to a network or [`Uds::create_network()`] to create one.
914    /// It will also return an error if the service is currently connected to a network as a spectator, as spectators cannot send data, only receive it.
915    ///
916    /// # Example
917    ///
918    /// ```
919    /// # let _runner = test_runner::GdbRunner::default();
920    /// # use std::error::Error;
921    /// # fn main() -> Result<(), Box<dyn Error>> {
922    /// #
923    /// use ctru::services::uds::{ConnectionType, NodeID, SendFlags, Uds};
924    /// let mut uds = Uds::new(None)?;
925    ///
926    /// let networks = uds.scan(b"HBW\x10", None, None)?;
927    /// uds.connect_network(&networks[0], b"udsdemo passphrase c186093cd2652741\0", ConnectionType::Client, 1)?;
928    /// uds.send_packet(b"Hello, World!", NodeID::Broadcast, 1, SendFlags::Default)?;
929    /// #
930    /// # Ok(())
931    /// # }
932    /// ```
933    #[doc(alias = "udsSendTo")]
934    pub fn send_packet(
935        &self,
936        packet: &[u8],
937        address: NodeID,
938        channel: u8,
939        flags: SendFlags,
940    ) -> Result<(), Error> {
941        if self.service_status() == ServiceStatus::Disconnected {
942            return Err(Error::NotConnected);
943        }
944
945        if self.context.unwrap().spectator {
946            return Err(Error::Spectator);
947        }
948
949        let code = ResultCode(unsafe {
950            ctru_sys::udsSendTo(
951                address.into(),
952                channel,
953                flags.bits(),
954                packet.as_ptr().cast(),
955                packet.len(),
956            )
957        });
958
959        if code.0
960            != ctru_sys::MAKERESULT(
961                ctru_sys::RL_STATUS as _,
962                ctru_sys::RS_OUTOFRESOURCE as _,
963                ctru_sys::RM_UDS as _,
964                ctru_sys::RD_BUSY as _,
965            )
966        {
967            code?;
968        }
969
970        Ok(())
971    }
972
973    /// Pull a packet from the network.
974    ///
975    /// # Errors
976    ///
977    /// This function will return an error if the service is currently neither connected to nor hosting a network.
978    /// See [`Uds::connect_network()`] to connect to a network or [`Uds::create_network()`] to create one.
979    ///
980    /// # Example
981    ///
982    /// ```
983    /// # let _runner = test_runner::GdbRunner::default();
984    /// # use std::error::Error;
985    /// # fn main() -> Result<(), Box<dyn Error>> {
986    /// #
987    /// use ctru::services::uds::{ConnectionType, Uds};
988    /// let mut uds = Uds::new(None)?;
989    ///
990    /// let networks = uds.scan(b"HBW\x10", None, None)?;
991    /// uds.connect_network(&networks[0], b"udsdemo passphrase c186093cd2652741\0", ConnectionType::Client, 1)?;
992    /// let packet = uds.pull_packet()?;
993    /// #
994    /// # Ok(())
995    /// # }
996    /// ```
997    #[doc(alias = "udsPullPacket")]
998    pub fn pull_packet(&self) -> Result<Option<(Vec<u8>, NodeID)>, Error> {
999        if self.service_status() == ServiceStatus::Disconnected {
1000            return Err(Error::NotConnected);
1001        }
1002
1003        let mut frame = MaybeUninit::<[u8; Self::RECV_FRAME_SIZE]>::zeroed();
1004
1005        let mut actual_size = MaybeUninit::uninit();
1006        let mut src_node_id = MaybeUninit::uninit();
1007
1008        ResultCode(unsafe {
1009            ctru_sys::udsPullPacket(
1010                &self.context.unwrap() as *const _,
1011                frame.as_mut_ptr().cast(),
1012                Self::RECV_FRAME_SIZE,
1013                actual_size.as_mut_ptr(),
1014                src_node_id.as_mut_ptr(),
1015            )
1016        })?;
1017
1018        let frame = unsafe { frame.assume_init() };
1019        let actual_size = unsafe { actual_size.assume_init() };
1020        let src_node_id = unsafe { src_node_id.assume_init() };
1021
1022        Ok(if actual_size == 0 {
1023            None
1024        } else {
1025            // TODO: to_vec() first, then truncate() and shrink_to_fit()?
1026            Some((
1027                frame[..actual_size].to_vec(),
1028                src_node_id
1029                    .try_into()
1030                    .expect("UDS service should always provide a valid NetworkNodeID"),
1031            ))
1032        })
1033    }
1034
1035    /// Create a new network.
1036    ///
1037    /// # Errors
1038    ///
1039    /// This function will return an error if the [`Uds`] service is already being used.
1040    ///
1041    /// # Example
1042    ///
1043    /// ```
1044    /// # let _runner = test_runner::GdbRunner::default();
1045    /// # use std::error::Error;
1046    /// # fn main() -> Result<(), Box<dyn Error>> {
1047    /// #
1048    /// use ctru::services::uds::Uds;
1049    /// let mut uds = Uds::new(None)?;
1050    ///
1051    /// uds.create_network(b"HBW\x10", None, None, b"udsdemo passphrase c186093cd2652741\0", 1)?;
1052    /// #
1053    /// # Ok(())
1054    /// # }
1055    /// ```
1056    #[doc(alias = "udsCreateNetwork")]
1057    pub fn create_network(
1058        &mut self,
1059        comm_id: &[u8; 4],
1060        additional_id: Option<u8>,
1061        max_nodes: Option<u8>,
1062        passphrase: &[u8],
1063        channel: u8,
1064    ) -> crate::Result<()> {
1065        let mut network = MaybeUninit::uninit();
1066        unsafe {
1067            ctru_sys::udsGenerateDefaultNetworkStruct(
1068                network.as_mut_ptr(),
1069                u32::from_be_bytes(*comm_id),
1070                additional_id.unwrap_or(0),
1071                max_nodes.unwrap_or(Self::MAX_NODES).min(Self::MAX_NODES),
1072            )
1073        };
1074
1075        let network = unsafe { network.assume_init() };
1076
1077        let mut context = MaybeUninit::uninit();
1078
1079        ResultCode(unsafe {
1080            ctru_sys::udsCreateNetwork(
1081                &network as *const _,
1082                passphrase.as_ptr().cast(),
1083                passphrase.len(),
1084                context.as_mut_ptr(),
1085                channel,
1086                Self::RECV_BUF_SIZE,
1087            )
1088        })?;
1089
1090        let context = unsafe { context.assume_init() };
1091
1092        self.network.replace(network);
1093
1094        self.context.replace(context);
1095
1096        Ok(())
1097    }
1098
1099    /// Destroy the current network.
1100    ///
1101    /// # Errors
1102    ///
1103    /// This function will return an error if no network has been created.
1104    /// See [`Uds::create_network()`] to create a network.
1105    ///
1106    /// # Example
1107    ///
1108    /// ```
1109    /// # let _runner = test_runner::GdbRunner::default();
1110    /// # use std::error::Error;
1111    /// # fn main() -> Result<(), Box<dyn Error>> {
1112    /// #
1113    /// use ctru::services::uds::Uds;
1114    /// let mut uds = Uds::new(None)?;
1115    ///
1116    /// uds.create_network(b"HBW\x10", None, None, b"udsdemo passphrase c186093cd2652741\0", 1)?;
1117    /// uds.destroy_network()?;
1118    /// #
1119    /// # Ok(())
1120    /// # }
1121    /// ```
1122    #[doc(alias = "udsDestroyNetwork")]
1123    pub fn destroy_network(&mut self) -> Result<(), Error> {
1124        if self.service_status() != ServiceStatus::Server {
1125            return Err(Error::NoNetwork);
1126        }
1127
1128        // should always be true
1129        if self.context.is_some() {
1130            self.unbind_context()?;
1131        }
1132
1133        ResultCode(unsafe { ctru_sys::udsDestroyNetwork() })?;
1134
1135        self.network = None;
1136
1137        Ok(())
1138    }
1139
1140    /// Set the app data for the currently hosted network.
1141    ///
1142    /// # Errors
1143    ///
1144    /// This function will return an error if no network has been created.
1145    /// See [`Uds::create_network()`] to create a network.
1146    /// This function will also return an error if the provided buffer is too large (see [`Uds::MAX_APPDATA_SIZE`]).
1147    ///
1148    /// # Example
1149    ///
1150    /// ```
1151    /// # let _runner = test_runner::GdbRunner::default();
1152    /// # use std::error::Error;
1153    /// # fn main() -> Result<(), Box<dyn Error>> {
1154    /// #
1155    /// use ctru::services::uds::Uds;
1156    /// let mut uds = Uds::new(None)?;
1157    ///
1158    /// uds.create_network(b"HBW\x10", None, None, b"udsdemo passphrase c186093cd2652741\0", 1)?;
1159    /// uds.set_appdata(b"Test appdata.\0")?;
1160    /// #
1161    /// # Ok(())
1162    /// # }
1163    /// ```
1164    #[doc(alias = "udsSetApplicationData")]
1165    pub fn set_appdata(&self, data: &[u8]) -> Result<(), Error> {
1166        if self.service_status() != ServiceStatus::Server {
1167            return Err(Error::NoNetwork);
1168        }
1169
1170        if data.len() > Self::MAX_APPDATA_SIZE {
1171            return Err(Error::TooMuchAppData);
1172        }
1173
1174        ResultCode(unsafe { ctru_sys::udsSetApplicationData(data.as_ptr().cast(), data.len()) })?;
1175
1176        Ok(())
1177    }
1178
1179    /// Wait for a bind event to occur.
1180    ///
1181    /// If `next` is `true`, discard the current event (if any) and wait for the next one.
1182    ///
1183    /// If `wait` is `true`, block until an event is signalled, else return `false` if no event.
1184    ///
1185    /// Always returns `true`, unless `wait` is `false` and no event has been signalled.
1186    ///
1187    /// # Errors
1188    ///
1189    /// This function will return an error if the service is currently neither connected to nor hosting a network.
1190    /// See [`Uds::connect_network()`] to connect to a network or [`Uds::create_network()`] to create one.
1191    ///
1192    /// # Example
1193    ///
1194    /// ```
1195    /// # let _runner = test_runner::GdbRunner::default();
1196    /// # use std::error::Error;
1197    /// # fn main() -> Result<(), Box<dyn Error>> {
1198    /// #
1199    /// use ctru::services::uds::{ConnectionType, Uds};
1200    /// let mut uds = Uds::new(None)?;
1201    ///
1202    /// let networks = uds.scan(b"HBW\x10", None, None)?;
1203    /// uds.connect_network(&networks[0], b"udsdemo passphrase c186093cd2652741\0", ConnectionType::Client, 1)?;
1204    /// if uds.wait_data_available(false, false)? {
1205    ///     println!("Data available");
1206    /// }
1207    /// #
1208    /// # Ok(())
1209    /// # }
1210    /// ```
1211    #[doc(alias = "udsWaitConnectionStatusEvent")]
1212    pub fn wait_data_available(&self, next: bool, wait: bool) -> Result<bool, Error> {
1213        if self.service_status() == ServiceStatus::Disconnected {
1214            return Err(Error::NotConnected);
1215        }
1216
1217        Ok(unsafe {
1218            ctru_sys::udsWaitDataAvailable(&self.context.unwrap() as *const _, next, wait)
1219        })
1220    }
1221
1222    /// Eject a client from the network.
1223    ///
1224    /// # Errors
1225    ///
1226    /// This function will return an error if no network has been created.
1227    /// See [`Uds::create_network()`] to create a network.
1228    ///
1229    /// # Example
1230    ///
1231    /// ```
1232    /// # let _runner = test_runner::GdbRunner::default();
1233    /// # use std::error::Error;
1234    /// # fn main() -> Result<(), Box<dyn Error>> {
1235    /// #
1236    /// use ctru::services::uds::{NodeID, Uds};
1237    /// let mut uds = Uds::new(None)?;
1238    ///
1239    /// uds.create_network(b"HBW\x10", None, None, b"udsdemo passphrase c186093cd2652741\0", 1)?;
1240    /// uds.eject_client(NodeID::Node(2))?;
1241    /// #
1242    /// # Ok(())
1243    /// # }
1244    /// ```
1245    #[doc(alias = "udsEjectClient")]
1246    pub fn eject_client(&self, address: NodeID) -> Result<(), Error> {
1247        if self.service_status() != ServiceStatus::Server {
1248            return Err(Error::NoNetwork);
1249        }
1250
1251        ResultCode(unsafe { ctru_sys::udsEjectClient(address.into()) })?;
1252
1253        Ok(())
1254    }
1255
1256    /// Allow or disallow spectators on the network.
1257    ///
1258    /// Disallowing spectators will disconnect all spectators currently observing the network.
1259    ///
1260    /// # Errors
1261    ///
1262    /// This function will return an error if no network has been created.
1263    /// See [`Uds::create_network()`] to create a network.
1264    ///
1265    /// # Example
1266    ///
1267    /// ```
1268    /// # let _runner = test_runner::GdbRunner::default();
1269    /// # use std::error::Error;
1270    /// # fn main() -> Result<(), Box<dyn Error>> {
1271    /// #
1272    /// use ctru::services::uds::Uds;
1273    /// let mut uds = Uds::new(None)?;
1274    ///
1275    /// uds.create_network(b"HBW\x10", None, None, b"udsdemo passphrase c186093cd2652741\0", 1)?;
1276    /// uds.allow_spectators(false)?;
1277    /// #
1278    /// # Ok(())
1279    /// # }
1280    /// ```
1281    #[doc(alias = "udsEjectSpectator")]
1282    #[doc(alias = "udsAllowSpectators")]
1283    pub fn allow_spectators(&mut self, allow: bool) -> Result<(), Error> {
1284        if self.service_status() != ServiceStatus::Server {
1285            return Err(Error::NoNetwork);
1286        }
1287
1288        ResultCode(unsafe {
1289            if allow {
1290                ctru_sys::udsAllowSpectators()
1291            } else {
1292                ctru_sys::udsEjectSpectator()
1293            }
1294        })?;
1295
1296        Ok(())
1297    }
1298
1299    /// Allow or disallow new clients on the network.
1300    ///
1301    /// Disallowing new clients will not disconnect any currently connected clients.
1302    ///
1303    /// # Errors
1304    ///
1305    /// This function will return an error if no network has been created.
1306    /// See [`Uds::create_network()`] to create a network.
1307    ///
1308    /// # Example
1309    ///
1310    /// ```
1311    /// # let _runner = test_runner::GdbRunner::default();
1312    /// # use std::error::Error;
1313    /// # fn main() -> Result<(), Box<dyn Error>> {
1314    /// #
1315    /// use ctru::services::uds::Uds;
1316    /// let mut uds = Uds::new(None)?;
1317    ///
1318    /// uds.create_network(b"HBW\x10", None, None, b"udsdemo passphrase c186093cd2652741\0", 1)?;
1319    /// uds.allow_new_clients(false)?;
1320    /// #
1321    /// # Ok(())
1322    /// # }
1323    /// ```
1324    #[doc(alias = "udsSetNewConnectionsBlocked")]
1325    pub fn allow_new_clients(&mut self, allow: bool) -> Result<(), Error> {
1326        if self.service_status() != ServiceStatus::Server {
1327            return Err(Error::NoNetwork);
1328        }
1329
1330        ResultCode(unsafe { ctru_sys::udsSetNewConnectionsBlocked(!allow, true, false) })?;
1331
1332        Ok(())
1333    }
1334
1335    /// Returns the [`NodeInfo`] struct for the specified network node.
1336    ///
1337    /// # Errors
1338    ///
1339    /// This function will return an error if [`NodeID::None`] or [`NodeID::Broadcast`] is passed.
1340    ///
1341    /// # Example
1342    ///
1343    /// ```
1344    /// # let _runner = test_runner::GdbRunner::default();
1345    /// # use std::error::Error;
1346    /// # fn main() -> Result<(), Box<dyn Error>> {
1347    /// #
1348    /// use ctru::services::uds::{NodeID, Uds};
1349    /// let mut uds = Uds::new(None)?;
1350    ///
1351    /// uds.create_network(b"HBW\x10", None, None, b"udsdemo passphrase c186093cd2652741\0", 1)?;
1352    /// let node_info = uds.node_info(NodeID::Node(2))?;
1353    /// #
1354    /// # Ok(())
1355    /// # }
1356    /// ```
1357    #[doc(alias = "udsGetNodeInformation")]
1358    pub fn node_info(&self, address: NodeID) -> Result<NodeInfo, Error> {
1359        let NodeID::Node(node) = address else {
1360            return Err(Error::NotANode);
1361        };
1362
1363        let mut info = MaybeUninit::uninit();
1364
1365        ResultCode(unsafe { ctru_sys::udsGetNodeInformation(node as u16, info.as_mut_ptr()) })?;
1366
1367        let info = unsafe { info.assume_init() };
1368
1369        Ok(info.into())
1370    }
1371}
1372
1373impl Drop for Uds {
1374    #[doc(alias = "udsExit")]
1375    fn drop(&mut self) {
1376        match self.service_status() {
1377            ServiceStatus::Client => self.disconnect_network().unwrap(),
1378            ServiceStatus::Server => self.destroy_network().unwrap(),
1379            _ => {}
1380        };
1381        // ctru_sys::udsExit() is called by the ServiceHandle
1382    }
1383}