diff --git a/ctru-rs/examples/local-networking.rs b/ctru-rs/examples/local-networking.rs index 75f2d0e..0138659 100644 --- a/ctru-rs/examples/local-networking.rs +++ b/ctru-rs/examples/local-networking.rs @@ -5,7 +5,30 @@ use ctru::prelude::*; use ctru::services::uds::*; -fn main() { +fn handle_status_event(uds: &Uds, prev_node_mask: u16) -> ctru::Result { + println!("Connection status event signalled"); + let status = uds.get_connection_status()?; + println!("Status: {status:#02X?}"); + let left = prev_node_mask & (status.node_bitmask ^ prev_node_mask); + let joined = status.node_bitmask & (status.node_bitmask ^ prev_node_mask); + for i in 0..16 { + if left & (1 << i) != 0 { + println!("Node {} disconnected", i + 1); + } + } + for i in 0..16 { + if joined & (1 << i) != 0 { + println!( + "Node {} connected: {:?}", + i + 1, + uds.get_node_info(NodeID::Node(i + 1)) + ); + } + } + Ok(status.node_bitmask) +} + +fn main() -> Result<(), Error> { let apt = Apt::new().unwrap(); let mut hid = Hid::new().unwrap(); let gfx = Gfx::new().unwrap(); @@ -40,6 +63,8 @@ fn main() { let mut channel = 0; let data_channel = 1; + let mut prev_node_mask = 0; + while apt.main_loop() { gfx.wait_for_vblank(); @@ -53,9 +78,11 @@ fn main() { if hid.keys_down().contains(KeyPad::A) { state = State::Scanning; console.clear(); + prev_node_mask = 0; } else if hid.keys_down().contains(KeyPad::B) { state = State::Create; console.clear(); + prev_node_mask = 0; } } State::Scanning => { @@ -126,9 +153,7 @@ fn main() { } } State::Connect => { - let appdata = uds - .get_network_appdata(&networks[selected_network], None) - .unwrap(); + let appdata = uds.get_network_appdata(&networks[selected_network], None)?; println!("App data: {:02X?}", appdata); if let Err(e) = uds.connect_network( @@ -142,16 +167,14 @@ fn main() { state = State::Initialised; println!("Press A to start scanning or B to create a new network"); } else { - channel = uds.get_channel().unwrap(); + channel = uds.get_channel()?; println!("Connected using channel {}", channel); - let appdata = uds.get_appdata(None).unwrap(); + let appdata = uds.get_appdata(None)?; println!("App data: {:02X?}", appdata); - if uds.wait_status_event(false, false).unwrap() { - println!("Connection status event signalled"); - let status = uds.get_connection_status().unwrap(); - println!("Status: {status:#02X?}"); + if uds.wait_status_event(false, false)? { + prev_node_mask = handle_status_event(&uds, prev_node_mask)?; } println!("Press A to stop data transfer"); @@ -170,14 +193,12 @@ fn main() { ); } - if uds.wait_status_event(false, false).unwrap() { - println!("Connection status event signalled"); - let status = uds.get_connection_status().unwrap(); - println!("Status: {status:#02X?}"); + if uds.wait_status_event(false, false)? { + prev_node_mask = handle_status_event(&uds, prev_node_mask)?; } if hid.keys_down().contains(KeyPad::A) { - uds.disconnect_network().unwrap(); + uds.disconnect_network()?; state = State::Initialised; console.clear(); println!("Press A to start scanning or B to create a new network"); @@ -189,15 +210,14 @@ fn main() { NodeID::Broadcast, data_channel, SendFlags::Default, - ) - .unwrap(); + )?; } } } Err(e) => { - uds.disconnect_network().unwrap(); + uds.disconnect_network()?; console.clear(); - eprintln!("Error while grabbing packet from network: {e:#?}"); + eprintln!("Error while grabbing packet from network: {e}"); state = State::Initialised; println!("Press A to start scanning or B to create a new network"); } @@ -221,7 +241,7 @@ fn main() { .chain(std::iter::repeat(0).take(3)) .collect::>(); - uds.set_appdata(&appdata).unwrap(); + uds.set_appdata(&appdata)?; println!("Press A to stop data transfer"); state = State::Created; @@ -246,14 +266,12 @@ fn main() { ); } - if uds.wait_status_event(false, false).unwrap() { - println!("Connection status event signalled"); - let status = uds.get_connection_status().unwrap(); - println!("Status: {status:#02X?}"); + if uds.wait_status_event(false, false)? { + prev_node_mask = handle_status_event(&uds, prev_node_mask)?; } if hid.keys_down().contains(KeyPad::A) { - uds.destroy_network().unwrap(); + uds.destroy_network()?; state = State::Initialised; console.clear(); println!("Press A to start scanning or B to create a new network"); @@ -264,14 +282,13 @@ fn main() { NodeID::Broadcast, data_channel, SendFlags::Default, - ) - .unwrap(); + )?; } } Err(e) => { - uds.destroy_network().unwrap(); + uds.destroy_network()?; console.clear(); - eprintln!("Error while grabbing packet from network: {e:#?}"); + eprintln!("Error while grabbing packet from network: {e}"); state = State::Initialised; println!("Press A to start scanning or B to create a new network"); } @@ -279,4 +296,6 @@ fn main() { } } } + + Ok(()) } diff --git a/ctru-rs/src/services/uds.rs b/ctru-rs/src/services/uds.rs index 33f6dc3..98fd630 100644 --- a/ctru-rs/src/services/uds.rs +++ b/ctru-rs/src/services/uds.rs @@ -5,8 +5,9 @@ #![doc(alias = "network")] #![doc(alias = "dlplay")] +use std::error::Error as StdError; use std::ffi::CString; -use std::fmt::Debug; +use std::fmt::{Debug, Display}; use std::mem::MaybeUninit; use std::ops::FromResidual; use std::ptr::null; @@ -47,6 +48,8 @@ pub enum Error { NoNetwork, /// The provided app data buffer was too large. TooMuchAppData, + /// The provided node ID does not reference a specific node. + NotANode, /// ctru-rs error Lib(crate::Error), } @@ -63,6 +66,29 @@ impl FromResidual for Result { } } +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::UsernameTooLong => + "provided username was too long (max 10 bytes, not code points)".into(), + Self::UsernameContainsNull => "provided username contained a NULL byte".into(), + Self::NotConnected => "not connected to a network".into(), + Self::NoContext => "no context bound".into(), + Self::Spectator => "cannot send data on a network as a spectator".into(), + Self::NoNetwork => "not hosting a network".into(), + Self::TooMuchAppData => "provided too much app data (max 200 bytes)".into(), + Self::NotANode => "provided node ID was non-specific".into(), + Self::Lib(e) => format!("ctru-rs error: {e}"), + } + ) + } +} + +impl StdError for Error {} + /// Possible types of connection to a network /// #[doc(alias = "udsConnectionType")] @@ -558,7 +584,7 @@ impl Uds { passphrase.as_ptr().cast(), passphrase.len(), context.as_mut_ptr(), - ctru_sys::UDS_BROADCAST_NETWORKNODEID as u16, + NodeID::Broadcast.into(), connection_type as u32, channel, Self::RECV_BUF_SIZE, @@ -784,12 +810,12 @@ impl Uds { /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # - /// use ctru::services::uds::{ConnectionType, SendFlags, Uds}; + /// use ctru::services::uds::{ConnectionType, NodeID, SendFlags, Uds}; /// let mut uds = Uds::new(None)?; /// /// let networks = uds.scan(b"HBW\x10", None, None)?; /// uds.connect_network(&networks[0], b"udsdemo passphrase c186093cd2652741\0", ConnectionType::Client, 1)?; - /// uds.send_packet(b"Hello, World!", ctru_sys::UDS_BROADCAST_NETWORKNODEID as u16, 1, SendFlags::Default)?; + /// uds.send_packet(b"Hello, World!", NodeID::Broadcast, 1, SendFlags::Default)?; /// # /// # Ok(()) /// # } @@ -848,7 +874,7 @@ impl Uds { /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # - /// use ctru::services::uds::{ConnectionType, SendFlags, Uds}; + /// use ctru::services::uds::{ConnectionType, Uds}; /// let mut uds = Uds::new(None)?; /// /// let networks = uds.scan(b"HBW\x10", None, None)?; @@ -909,7 +935,7 @@ impl Uds { /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # - /// use ctru::services::uds::{ConnectionType, SendFlags, Uds}; + /// use ctru::services::uds::Uds; /// let mut uds = Uds::new(None)?; /// /// uds.create_network(b"HBW\x10", None, None, b"udsdemo passphrase c186093cd2652741\0", 1)?; @@ -974,7 +1000,7 @@ impl Uds { /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # - /// use ctru::services::uds::{ConnectionType, SendFlags, Uds}; + /// use ctru::services::uds::Uds; /// let mut uds = Uds::new(None)?; /// /// uds.create_network(b"HBW\x10", None, None, b"udsdemo passphrase c186093cd2652741\0", 1)?; @@ -1016,7 +1042,7 @@ impl Uds { /// # use std::error::Error; /// # fn main() -> Result<(), Box> { /// # - /// use ctru::services::uds::{ConnectionType, SendFlags, Uds}; + /// use ctru::services::uds::Uds; /// let mut uds = Uds::new(None)?; /// /// uds.create_network(b"HBW\x10", None, None, b"udsdemo passphrase c186093cd2652741\0", 1)?; @@ -1039,6 +1065,199 @@ impl Uds { Ok(()) } + + /// Wait for a bind event to occur. + /// + /// If `next` is `true`, discard the current event (if any) and wait for the next one. + /// + /// If `wait` is `true`, block until an event is signalled, else return `false` if no event. + /// + /// Always returns `true`, unless `wait` is `false` and no event has been signalled. + /// + /// # Errors + /// + /// This function will return an error if the service is currently neither connected to nor hosting a network. + /// See [`Uds::connect_network()`] to connect to a network or [`Uds::create_network()`] to create one. + /// + /// # Example + /// + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::uds::{ConnectionType, Uds}; + /// let mut uds = Uds::new(None)?; + /// + /// let networks = uds.scan(b"HBW\x10", None, None)?; + /// uds.connect_network(&networks[0], b"udsdemo passphrase c186093cd2652741\0", ConnectionType::Client, 1)?; + /// if uds.wait_data_available(false, false)? { + /// println!("Data available"); + /// } + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "udsWaitConnectionStatusEvent")] + pub fn wait_data_available(&self, next: bool, wait: bool) -> Result { + if self.service_status() == ServiceStatus::Disconnected { + return Err(Error::NotConnected); + } + + Ok(unsafe { + ctru_sys::udsWaitDataAvailable(&self.context.unwrap() as *const _, next, wait) + }) + } + + /// Eject a client from the network. + /// + /// # Errors + /// + /// This function will return an error if no network has been created. + /// See [`Uds::create_network()`] to create a network. + /// + /// # Example + /// + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::uds::{NodeID, Uds}; + /// let mut uds = Uds::new(None)?; + /// + /// uds.create_network(b"HBW\x10", None, None, b"udsdemo passphrase c186093cd2652741\0", 1)?; + /// uds.eject_client(NodeID::Node(2))?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "udsEjectClient")] + pub fn eject_client(&self, address: NodeID) -> Result<(), Error> { + if self.service_status() != ServiceStatus::Server { + return Err(Error::NoNetwork); + } + + ResultCode(unsafe { ctru_sys::udsEjectClient(address.into()) })?; + + Ok(()) + } + + /// Allow or disallow spectators on the network. + /// + /// Disallowing spectators will disconnect all spectators currently observing the network. + /// + /// # Errors + /// + /// This function will return an error if no network has been created. + /// See [`Uds::create_network()`] to create a network. + /// + /// # Example + /// + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::uds::Uds; + /// let mut uds = Uds::new(None)?; + /// + /// uds.create_network(b"HBW\x10", None, None, b"udsdemo passphrase c186093cd2652741\0", 1)?; + /// uds.allow_spectators(false)?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "udsEjectSpectator")] + #[doc(alias = "udsAllowSpectators")] + pub fn allow_spectators(&mut self, allow: bool) -> Result<(), Error> { + if self.service_status() != ServiceStatus::Server { + return Err(Error::NoNetwork); + } + + ResultCode(unsafe { + if allow { + ctru_sys::udsAllowSpectators() + } else { + ctru_sys::udsEjectSpectator() + } + })?; + + Ok(()) + } + + /// Allow or disallow new clients on the network. + /// + /// Disallowing new clients will not disconnect any currently connected clients. + /// + /// # Errors + /// + /// This function will return an error if no network has been created. + /// See [`Uds::create_network()`] to create a network. + /// + /// # Example + /// + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::uds::Uds; + /// let mut uds = Uds::new(None)?; + /// + /// uds.create_network(b"HBW\x10", None, None, b"udsdemo passphrase c186093cd2652741\0", 1)?; + /// uds.allow_new_clients(false)?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "udsSetNewConnectionsBlocked")] + pub fn allow_new_clients(&mut self, allow: bool) -> Result<(), Error> { + if self.service_status() != ServiceStatus::Server { + return Err(Error::NoNetwork); + } + + ResultCode(unsafe { ctru_sys::udsSetNewConnectionsBlocked(!allow, true, false) })?; + + Ok(()) + } + + /// Returns the [`NodeInfo`] struct for the specified network node. + /// + /// # Errors + /// + /// This function will return an error if [`NodeID::None`] or [`NodeID::Broadcast`] is passed. + /// + /// # Example + /// + /// ``` + /// # let _runner = test_runner::GdbRunner::default(); + /// # use std::error::Error; + /// # fn main() -> Result<(), Box> { + /// # + /// use ctru::services::uds::{NodeID, Uds}; + /// let mut uds = Uds::new(None)?; + /// + /// uds.create_network(b"HBW\x10", None, None, b"udsdemo passphrase c186093cd2652741\0", 1)?; + /// let node_info = uds.get_node_info(NodeID::Node(2))?; + /// # + /// # Ok(()) + /// # } + /// ``` + #[doc(alias = "udsGetNodeInformation")] + pub fn get_node_info(&self, address: NodeID) -> Result { + let NodeID::Node(node) = address else { + return Err(Error::NotANode); + }; + + let mut info = MaybeUninit::uninit(); + + ResultCode(unsafe { ctru_sys::udsGetNodeInformation(node as u16, info.as_mut_ptr()) })?; + + let info = unsafe { info.assume_init() }; + + Ok(info.into()) + } } impl Drop for Uds {