Browse Source

Implement remaining UDS functions (mostly untested)

pull/156/head
Jhynjhiruu 11 months ago
parent
commit
5feb906690
  1. 77
      ctru-rs/examples/local-networking.rs
  2. 235
      ctru-rs/src/services/uds.rs

77
ctru-rs/examples/local-networking.rs

@ -5,7 +5,30 @@ @@ -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<u16> {
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() { @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -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() { @@ -221,7 +241,7 @@ fn main() {
.chain(std::iter::repeat(0).take(3))
.collect::<Vec<_>>();
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() { @@ -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() { @@ -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() { @@ -279,4 +296,6 @@ fn main() {
}
}
}
Ok(())
}

235
ctru-rs/src/services/uds.rs

@ -5,8 +5,9 @@ @@ -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 { @@ -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<T> FromResidual<crate::Error> for Result<T, Error> { @@ -63,6 +66,29 @@ impl<T> FromResidual<crate::Error> for Result<T, Error> {
}
}
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 { @@ -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 { @@ -784,12 +810,12 @@ impl Uds {
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// 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 { @@ -848,7 +874,7 @@ impl Uds {
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// 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 { @@ -909,7 +935,7 @@ impl Uds {
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// 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 { @@ -974,7 +1000,7 @@ impl Uds {
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// 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 { @@ -1016,7 +1042,7 @@ impl Uds {
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// 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 { @@ -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<dyn Error>> {
/// #
/// 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<bool, Error> {
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<dyn Error>> {
/// #
/// 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<dyn Error>> {
/// #
/// 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<dyn Error>> {
/// #
/// 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<dyn Error>> {
/// #
/// 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<NodeInfo, Error> {
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 {

Loading…
Cancel
Save