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}