Browse Source

Implement UDS service with example

pull/156/head
Jhynjhiruu 11 months ago
parent
commit
f065d0da3d
  1. 1
      ctru-rs/Cargo.toml
  2. 282
      ctru-rs/examples/local-networking.rs
  3. 1
      ctru-rs/src/lib.rs
  4. 2
      ctru-rs/src/services/ir_user.rs
  5. 1
      ctru-rs/src/services/mod.rs
  6. 960
      ctru-rs/src/services/uds.rs

1
ctru-rs/Cargo.toml

@ -24,6 +24,7 @@ shim-3ds = { git = "https://github.com/rust3ds/shim-3ds.git" } @@ -24,6 +24,7 @@ shim-3ds = { git = "https://github.com/rust3ds/shim-3ds.git" }
pthread-3ds = { git = "https://github.com/rust3ds/pthread-3ds.git" }
libc = "0.2.121"
bitflags = "2.3.3"
macaddr = "1.0.1"
[build-dependencies]
toml = "0.5"

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

@ -0,0 +1,282 @@ @@ -0,0 +1,282 @@
//! Local networking example.
//!
//! This example showcases local networking using the UDS module.
use ctru::prelude::*;
use ctru::services::uds::*;
fn main() {
let apt = Apt::new().unwrap();
let mut hid = Hid::new().unwrap();
let gfx = Gfx::new().unwrap();
let console = Console::new(gfx.top_screen.borrow_mut());
println!("Local networking demo");
let mut uds = Uds::new(None).unwrap();
println!("UDS initialised");
enum State {
Initialised,
Scanning,
DrawList,
List,
Connect,
Connected,
Create,
Created,
}
let mut state = State::Initialised;
println!("Press A to start scanning or B to create a new network");
let mut networks = vec![];
let mut selected_network = 0;
let mut mode = ConnectionType::Client;
let mut channel = 0;
let data_channel = 1;
while apt.main_loop() {
gfx.wait_for_vblank();
hid.scan_input();
if hid.keys_down().contains(KeyPad::START) {
break;
}
match state {
State::Initialised => {
if hid.keys_down().contains(KeyPad::A) {
state = State::Scanning;
console.clear();
} else if hid.keys_down().contains(KeyPad::B) {
state = State::Create;
console.clear();
}
}
State::Scanning => {
println!("Scanning...");
let nwks = uds.scan(b"HBW\x10", None, None);
match nwks {
Ok(n) => {
if n.is_empty() {
state = State::Initialised;
console.clear();
println!("Scanned successfully; no networks found");
println!("Press A to start scanning or B to create a new network");
} else {
networks = n;
selected_network = 0;
state = State::DrawList;
}
}
Err(e) => {
state = State::Initialised;
console.clear();
eprintln!("Error while scanning: {e}");
println!("Press A to start scanning or B to create a new network");
}
}
}
State::DrawList => {
console.clear();
println!(
"Scanned successfully; {} network{} found",
networks.len(),
if networks.len() == 1 { "" } else { "s" }
);
println!("D-Pad to select, A to connect as client, R + A to connect as spectator, B to create a new network");
for (index, n) in networks.iter().enumerate() {
println!(
"{} Username: {}",
if index == selected_network { ">" } else { " " },
n.nodes[0].as_ref().unwrap().username
);
}
state = State::List;
}
State::List => {
if hid.keys_down().contains(KeyPad::UP) && selected_network > 0 {
selected_network -= 1;
state = State::DrawList;
} else if hid.keys_down().contains(KeyPad::DOWN)
&& selected_network < networks.len() - 1
{
selected_network += 1;
state = State::DrawList;
} else if hid.keys_down().contains(KeyPad::A) {
state = State::Connect;
mode = if hid.keys_held().contains(KeyPad::R) {
ConnectionType::Spectator
} else {
ConnectionType::Client
};
} else if hid.keys_down().contains(KeyPad::B) {
state = State::Create;
}
}
State::Connect => {
let appdata = uds
.get_network_appdata(&networks[selected_network], None)
.unwrap();
println!("App data: {:02X?}", appdata);
if let Err(e) = uds.connect_network(
&networks[selected_network],
b"udsdemo passphrase c186093cd2652741\0",
mode,
data_channel,
) {
console.clear();
eprintln!("Error while connecting to network: {e}");
state = State::Initialised;
println!("Press A to start scanning or B to create a new network");
} else {
channel = uds.get_channel().unwrap();
println!("Connected using channel {}", channel);
let appdata = uds.get_appdata(None).unwrap();
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?}");
}
println!("Press A to stop data transfer");
state = State::Connected;
}
}
State::Connected => {
let packet = uds.pull_packet();
match packet {
Ok(p) => {
if let Some((pkt, node)) = p {
println!(
"{:02X}{:02X}{:02X}{:02X} from {:04X}",
pkt[0], pkt[1], pkt[2], pkt[3], node
);
}
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 hid.keys_down().contains(KeyPad::A) {
uds.disconnect_network().unwrap();
state = State::Initialised;
console.clear();
println!("Press A to start scanning or B to create a new network");
} else if !hid.keys_down().is_empty() || !hid.keys_up().is_empty() {
let transfer_data = hid.keys_held().bits();
if mode != ConnectionType::Spectator {
uds.send_packet(
&transfer_data.to_le_bytes(),
ctru_sys::UDS_BROADCAST_NETWORKNODEID as _,
data_channel,
SendFlags::Default,
)
.unwrap();
}
}
}
Err(e) => {
uds.disconnect_network().unwrap();
console.clear();
eprintln!("Error while grabbing packet from network: {e:#?}");
state = State::Initialised;
println!("Press A to start scanning or B to create a new network");
}
}
}
State::Create => {
console.clear();
println!("Creating network...");
match uds.create_network(
b"HBW\x10",
None,
None,
b"udsdemo passphrase c186093cd2652741\0",
data_channel,
) {
Ok(_) => {
let appdata = [0x69u8, 0x8a, 0x05, 0x5c]
.into_iter()
.chain((*b"Test appdata.").into_iter())
.chain(std::iter::repeat(0).take(3))
.collect::<Vec<_>>();
uds.set_appdata(&appdata).unwrap();
println!("Press A to stop data transfer");
state = State::Created;
}
Err(e) => {
console.clear();
eprintln!("Error while creating network: {e}");
state = State::Initialised;
println!("Press A to start scanning or B to create a new network");
}
}
}
State::Created => {
let packet = uds.pull_packet();
match packet {
Ok(p) => {
if let Some((pkt, node)) = p {
println!(
"{:02X}{:02X}{:02X}{:02X} from {:04X}",
pkt[0], pkt[1], pkt[2], pkt[3], node
);
}
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 hid.keys_down().contains(KeyPad::A) {
uds.destroy_network().unwrap();
state = State::Initialised;
console.clear();
println!("Press A to start scanning or B to create a new network");
} else if !hid.keys_down().is_empty() || !hid.keys_up().is_empty() {
let transfer_data = hid.keys_held().bits();
uds.send_packet(
&transfer_data.to_le_bytes(),
ctru_sys::UDS_BROADCAST_NETWORKNODEID as _,
data_channel,
SendFlags::Default,
)
.unwrap();
}
}
Err(e) => {
uds.destroy_network().unwrap();
console.clear();
eprintln!("Error while grabbing packet from network: {e:#?}");
state = State::Initialised;
println!("Press A to start scanning or B to create a new network");
}
}
}
}
}
}

1
ctru-rs/src/lib.rs

@ -21,6 +21,7 @@ @@ -21,6 +21,7 @@
#![feature(custom_test_frameworks)]
#![feature(try_trait_v2)]
#![feature(allocator_api)]
#![feature(new_uninit)]
#![test_runner(test_runner::run_gdb)] // TODO: does this make sense to have configurable?
#![doc(
html_favicon_url = "https://user-images.githubusercontent.com/11131775/225929072-2fa1741c-93ae-4b47-9bdf-af70f3d59910.png"

2
ctru-rs/src/services/ir_user.rs

@ -150,7 +150,7 @@ impl IrUser { @@ -150,7 +150,7 @@ impl IrUser {
shared_mem.shared_memory_layout,
);
Ok(())
Ok::<_, Error>(())
})()
.unwrap();
},

1
ctru-rs/src/services/mod.rs

@ -26,6 +26,7 @@ mod reference; @@ -26,6 +26,7 @@ mod reference;
pub mod soc;
pub mod sslc;
pub mod svc;
pub mod uds;
cfg_if::cfg_if! {
if #[cfg(all(feature = "romfs", romfs_exists))] {

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

@ -0,0 +1,960 @@ @@ -0,0 +1,960 @@
//! UDS (local networking) service.
//!
//! The UDS service is used to handle local networking, i.e. peer-to-peer networking used for local multiplayer.
//! 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.
#![doc(alias = "network")]
#![doc(alias = "dlplay")]
use std::ffi::CString;
use std::fmt::Debug;
use std::mem::MaybeUninit;
use std::ops::FromResidual;
use std::ptr::null;
use std::sync::Mutex;
use crate::error::ResultCode;
use crate::services::ServiceReference;
use bitflags::bitflags;
use macaddr::MacAddr6;
bitflags! {
/// Flags used for sending packets to a network.
#[allow(missing_docs)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct SendFlags: u8 {
/// According to libctru source, it's not really known what these do.
const Default = ctru_sys::UDS_SENDFLAG_Default as u8;
const Broadcast = ctru_sys::UDS_SENDFLAG_Broadcast as u8;
}
}
/// Error enum for generic errors within the [`Uds`] service.
#[non_exhaustive]
#[derive(Debug)]
pub enum Error {
/// The provided username was too long.
UsernameTooLong,
/// The provided username contained a NULL byte.
UsernameContainsNull,
/// Not connected to a network.
NotConnected,
/// No context bound.
NoContext,
/// Cannot send data on a network as a spectator.
Spectator,
/// No network created.
NoNetwork,
/// The provided app data buffer was too large.
TooMuchAppData,
/// ctru-rs error
Lib(crate::Error),
}
impl From<crate::Error> for Error {
fn from(value: crate::Error) -> Self {
Error::Lib(value)
}
}
impl<T> FromResidual<crate::Error> for Result<T, Error> {
fn from_residual(residual: crate::Error) -> Self {
Err(residual.into())
}
}
/// Possible types of connection to a network
///
#[doc(alias = "udsConnectionType")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum ConnectionType {
/// A normal client. Can push packets to the network.
Client = ctru_sys::UDSCONTYPE_Client,
/// A spectator. Cannot push packets to the network,
/// but doesn't need the passphrase to join.
Spectator = ctru_sys::UDSCONTYPE_Spectator,
}
impl From<ConnectionType> for u8 {
fn from(value: ConnectionType) -> Self {
value as Self
}
}
impl TryFrom<u8> for ConnectionType {
type Error = ();
fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
match value as u32 {
ctru_sys::UDSCONTYPE_Client => Ok(Self::Client),
ctru_sys::UDSCONTYPE_Spectator => Ok(Self::Spectator),
_ => Err(()),
}
}
}
/// Information about a network node.
/// Ported to Rust so that Debug can be derived on it.
#[allow(missing_docs)]
#[doc(alias = "udsNodeInfo")]
#[derive(Debug)]
pub struct NodeInfo {
pub uds_friendcodeseed: u64,
pub username: String,
pub unk_x1c: u16,
pub flag: u8,
pub pad_x1f: u8,
pub node_id: u16,
pub pad_x22: u16,
pub word_x24: u32,
}
impl From<ctru_sys::udsNodeInfo> for NodeInfo {
fn from(value: ctru_sys::udsNodeInfo) -> Self {
unsafe {
Self {
uds_friendcodeseed: value.uds_friendcodeseed,
username: String::from_utf16_lossy(
&value.__bindgen_anon_1.__bindgen_anon_1.username,
),
unk_x1c: value.__bindgen_anon_1.__bindgen_anon_1.unk_x1c,
flag: value.__bindgen_anon_1.__bindgen_anon_1.flag,
pad_x1f: value.__bindgen_anon_1.__bindgen_anon_1.pad_x1f,
node_id: value.NetworkNodeID,
pad_x22: value.pad_x22,
word_x24: value.word_x24,
}
}
}
}
/// Information returned from scanning for networks.
/// Ported to Rust so that Debug can be derived on it.
#[allow(missing_docs)]
#[doc(alias = "udsNetworkScanInfo")]
#[derive(Debug)]
pub struct NetworkScanInfo {
pub datareply_entry: ctru_sys::nwmBeaconDataReplyEntry,
pub network: ctru_sys::udsNetworkStruct,
pub nodes: [Option<NodeInfo>; 16],
}
impl From<ctru_sys::udsNetworkScanInfo> for NetworkScanInfo {
fn from(value: ctru_sys::udsNetworkScanInfo) -> Self {
Self {
datareply_entry: value.datareply_entry,
network: value.network,
nodes: value.nodes.map(|n| {
if n.uds_friendcodeseed != 0 {
Some(n.into())
} else {
None
}
}),
}
}
}
/// Status of the service handle.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ServiceStatus {
/// Not connected to or hosting a network.
Disconnected,
/// Connected to a network.
Client,
/// Hosting a network.
Server,
}
/// Handle to the UDS service.
pub struct Uds {
_service_handler: ServiceReference,
context: Option<ctru_sys::udsBindContext>,
network: Option<ctru_sys::udsNetworkStruct>,
}
static UDS_ACTIVE: Mutex<()> = Mutex::new(());
impl Uds {
/// Size of one frame.
const RECV_FRAME_SIZE: usize = ctru_sys::UDS_DATAFRAME_MAXSIZE as usize;
/// Size of receive buffer; max frame size * 8.
const RECV_BUF_SIZE: u32 = ctru_sys::UDS_DEFAULT_RECVBUFSIZE;
/// Shared memory size; must be slightly larger
/// than `RECV_BUF_SIZE`.
const SHAREDMEM_SIZE: usize = 0x3000;
/// Buffer used while scanning for networks.
/// This value is taken from the devkitPRO example.
const SCAN_BUF_SIZE: usize = 0x4000;
/// The maximum number of nodes that can ever be connected
/// to a network (16). Can be further limited.
const MAX_NODES: u8 = ctru_sys::UDS_MAXNODES as u8;
/// The maximum amount of app data any server can provide.
/// Limited by the size of a struct in libctru.
const MAX_APPDATA_SIZE: usize = 200;
/// Retrieve the current status of the service.
pub fn service_status(&self) -> ServiceStatus {
match (self.context, self.network) {
(None, None) => ServiceStatus::Disconnected,
(Some(_), None) => ServiceStatus::Client,
(Some(_), Some(_)) => ServiceStatus::Server,
_ => unreachable!(),
}
}
/// Initialise a new service handle.
/// No `new_with_buffer_size` function is provided, as there isn't really a
/// reason to use any size other than the default.
///
/// # Errors
///
/// This function will return an error if the [`Uds`] service is already being used,
/// or if the provided username is invalid (longer than 10 bytes or contains a NULL byte).
///
/// # Example
///
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::uds::Uds;
///
/// let uds = Uds::new(None)?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "udsInit")]
pub fn new(username: Option<&str>) -> Result<Self, Error> {
if let Some(n) = username {
if n.len() > 10 {
return Err(Error::UsernameTooLong);
}
}
let cstr = username.map(CString::new);
let cstr = if let Some(conv) = cstr {
match conv {
Ok(c) => Some(c),
Err(_) => return Err(Error::UsernameContainsNull),
}
} else {
None
};
let handler = ServiceReference::new(
&UDS_ACTIVE,
|| {
let ptr = cstr.map(|c| c.as_ptr()).unwrap_or(null());
ResultCode(unsafe { ctru_sys::udsInit(Self::SHAREDMEM_SIZE, ptr) })?;
Ok(())
},
|| unsafe {
ctru_sys::udsExit();
},
)?;
Ok(Self {
_service_handler: handler,
context: None,
network: None,
})
}
/// Scan the UDS service for all available beacons broadcasting with the given IDs.
///
/// This function must be called to obtain network objects that can later be connected to.
///
/// # Example
///
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::uds::Uds;
/// let uds = Uds::new(None)?;
///
/// let networks = uds.scan(b"HBW\x10", None, None)?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "udsScanBeacons")]
pub fn scan(
&self,
comm_id: &[u8; 4],
additional_id: Option<u8>,
whitelist_macaddr: Option<MacAddr6>,
) -> crate::Result<Vec<NetworkScanInfo>> {
// normally, I construct this on the stack, but this example seems to have the stack size
// set too small for that, which causes a segfault.
let mut scan_buf = Box::<[u8; Self::SCAN_BUF_SIZE]>::new_zeroed();
let mut networks = MaybeUninit::uninit();
let mut total_networks = MaybeUninit::uninit();
ResultCode(unsafe {
ctru_sys::udsScanBeacons(
scan_buf.as_mut_ptr().cast(),
Self::SCAN_BUF_SIZE,
networks.as_mut_ptr(),
total_networks.as_mut_ptr(),
u32::from_be_bytes(*comm_id),
additional_id.unwrap_or(0),
whitelist_macaddr
.map(|m| m.as_bytes().as_ptr())
.unwrap_or(null()),
self.service_status() == ServiceStatus::Client,
)
})?;
unsafe {
scan_buf.assume_init_drop();
}
let networks = unsafe { networks.assume_init() };
let total_networks = unsafe { total_networks.assume_init() };
let networks = if total_networks > 0 {
// Safety: `networks` is malloced in application memory with size = `total_networks`
unsafe { Vec::from_raw_parts(networks, total_networks, total_networks) }
.into_iter()
.map(<_ as Into<NetworkScanInfo>>::into)
.collect()
} else {
vec![]
};
Ok(networks)
}
/// Retrieve app data for a network which the service is not connected to.
///
/// # Example
///
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::uds::Uds;
/// let uds = Uds::new(None)?;
///
/// let networks = uds.scan(b"HBW\x10", None, None)?;
/// let appdata = uds.get_network_appdata(&networks[0], None)?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "udsGetNetworkStructApplicationData")]
pub fn get_network_appdata(
&self,
network: &NetworkScanInfo,
max_size: Option<usize>,
) -> crate::Result<Vec<u8>> {
let mut appdata_buffer = vec![
0u8;
max_size
.unwrap_or(Self::MAX_APPDATA_SIZE)
.min(Self::MAX_APPDATA_SIZE)
];
let mut actual_size = MaybeUninit::uninit();
ResultCode(unsafe {
ctru_sys::udsGetNetworkStructApplicationData(
&network.network as *const _,
appdata_buffer.as_mut_ptr().cast(),
appdata_buffer.len(),
actual_size.as_mut_ptr(),
)
})?;
let actual_size = unsafe { actual_size.assume_init() };
Ok(appdata_buffer[..actual_size].to_vec())
}
/// Retrieve app data for the currently connected network.
///
/// # Errors
///
/// This function will return an error if the service is not connected to a network.
/// See [`Uds::connect_network()`] to connect to a network.
///
/// # Example
///
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::uds::{ConnectionType, Uds};
/// let 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)?;
/// let appdata = uds.get_appdata(None)?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "udsGetApplicationData")]
pub fn get_appdata(&self, max_size: Option<usize>) -> Result<Vec<u8>, Error> {
if self.service_status() == ServiceStatus::Disconnected {
return Err(Error::NotConnected);
}
let mut appdata_buffer = vec![
0u8;
max_size
.unwrap_or(Self::MAX_APPDATA_SIZE)
.min(Self::MAX_APPDATA_SIZE)
];
let mut actual_size = MaybeUninit::uninit();
ResultCode(unsafe {
ctru_sys::udsGetApplicationData(
appdata_buffer.as_mut_ptr().cast(),
appdata_buffer.len(),
actual_size.as_mut_ptr(),
)
})?;
let actual_size = unsafe { actual_size.assume_init() };
Ok(appdata_buffer[..actual_size].to_vec())
}
/// Connect to a network.
///
/// # 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)?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "udsConnectNetwork")]
pub fn connect_network(
&mut self,
network: &NetworkScanInfo,
passphrase: &[u8],
connection_type: ConnectionType,
channel: u8,
) -> crate::Result<()> {
let mut context = MaybeUninit::uninit();
ResultCode(unsafe {
ctru_sys::udsConnectNetwork(
&network.network as *const _,
passphrase.as_ptr().cast(),
passphrase.len(),
context.as_mut_ptr(),
ctru_sys::UDS_BROADCAST_NETWORKNODEID as u16,
connection_type as u32,
channel,
Self::RECV_BUF_SIZE,
)
})?;
let context = unsafe { context.assume_init() };
self.context.replace(context);
Ok(())
}
/// Disconnect from a network.
///
/// # Errors
///
/// This function will return an error if the service is not connected to a network.
/// See [`Uds::connect_network()`] to connect to a network.
///
/// # 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)?;
/// uds.disconnect_network()?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "udsDisconnectNetwork")]
pub fn disconnect_network(&mut self) -> Result<(), Error> {
if self.service_status() != ServiceStatus::Client {
return Err(Error::NotConnected);
}
if self.context.is_some() {
self.unbind_context()?;
}
ResultCode(unsafe { ctru_sys::udsDisconnectNetwork() })?;
Ok(())
}
/// Unbind the connection context.
///
/// Normally, there's no reason to call this function,
/// since [`Uds::disconnect_network()`] and [`Uds::destroy_network()`] both automatically unbind their contexts.
///
/// # Errors
///
/// This function will return an error if no context is currently bound (i.e. the service is 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)?;
/// uds.unbind_context()?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "udsUnbind")]
pub fn unbind_context(&mut self) -> Result<(), Error> {
if let Some(mut ctx) = self.context {
ResultCode(unsafe { ctru_sys::udsUnbind(&mut ctx as *mut _) })?;
} else {
return Err(Error::NoContext);
}
self.context = None;
Ok(())
}
/// Returns the Wi-Fi channel currently in use.
///
/// # 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)?;
/// let channel = uds.get_channel()?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "udsGetChannel")]
pub fn get_channel(&self) -> Result<u8, Error> {
if self.service_status() == ServiceStatus::Disconnected {
return Err(Error::NotConnected);
}
let mut channel = MaybeUninit::uninit();
ResultCode(unsafe { ctru_sys::udsGetChannel(channel.as_mut_ptr()) })?;
let channel = unsafe { channel.assume_init() };
Ok(channel)
}
/// Wait for a ConnectionStatus 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_status_event(false, false)? {
/// println!("Event signalled");
/// }
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "udsWaitConnectionStatusEvent")]
pub fn wait_status_event(&self, next: bool, wait: bool) -> Result<bool, Error> {
if self.service_status() == ServiceStatus::Disconnected {
return Err(Error::NotConnected);
}
Ok(unsafe { ctru_sys::udsWaitConnectionStatusEvent(next, wait) })
}
/// Returns the current [`ctru_sys::udsConnectionStatus`] struct.
///
/// TODO: should this return an error if not connected?
///
/// # 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_status_event(false, false)? {
/// println!("Connection status event signalled");
/// let status = uds.get_connection_status()?;
/// println!("Status: {status:#X?}");
/// }
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "udsGetConnectionStatus")]
pub fn get_connection_status(&self) -> crate::Result<ctru_sys::udsConnectionStatus> {
let mut status = MaybeUninit::uninit();
ResultCode(unsafe { ctru_sys::udsGetConnectionStatus(status.as_mut_ptr()) })?;
let status = unsafe { status.assume_init() };
Ok(status)
}
/// Send a packet to the network.
///
/// TODO: max size?
///
/// # 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.
/// 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.
///
/// # Example
///
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::uds::{ConnectionType, 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)?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "udsSendTo")]
pub fn send_packet(
&self,
packet: &[u8],
to_nodes: u16,
channel: u8,
flags: SendFlags,
) -> Result<(), Error> {
if self.service_status() == ServiceStatus::Disconnected {
return Err(Error::NotConnected);
}
if self.context.unwrap().spectator {
return Err(Error::Spectator);
}
let code = ResultCode(unsafe {
ctru_sys::udsSendTo(
to_nodes,
channel,
flags.bits(),
packet.as_ptr().cast(),
packet.len(),
)
});
if code.0
!= ctru_sys::MAKERESULT(
ctru_sys::RL_STATUS as _,
ctru_sys::RS_OUTOFRESOURCE as _,
ctru_sys::RM_UDS as _,
ctru_sys::RD_BUSY as _,
)
{
code?;
}
Ok(())
}
/// Pull a packet from the network.
///
/// # 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, 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)?;
/// let packet = uds.pull_packet()?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "udsPullPacket")]
pub fn pull_packet(&self) -> Result<Option<(Vec<u8>, u16)>, Error> {
if self.service_status() == ServiceStatus::Disconnected {
return Err(Error::NotConnected);
}
let mut frame = MaybeUninit::<[u8; Self::RECV_FRAME_SIZE]>::zeroed();
let mut actual_size = MaybeUninit::uninit();
let mut src_node_id = MaybeUninit::uninit();
ResultCode(unsafe {
ctru_sys::udsPullPacket(
&self.context.unwrap() as *const _,
frame.as_mut_ptr().cast(),
Self::RECV_FRAME_SIZE,
actual_size.as_mut_ptr(),
src_node_id.as_mut_ptr(),
)
})?;
let frame = unsafe { frame.assume_init() };
let actual_size = unsafe { actual_size.assume_init() };
let src_node_id = unsafe { src_node_id.assume_init() };
Ok(if actual_size == 0 {
None
} else {
Some((frame[..actual_size].to_vec(), src_node_id))
})
}
/// Create a new network.
///
/// # Errors
///
/// This function will return an error if the [`Uds`] service is already being used.
///
/// # Example
///
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::uds::{ConnectionType, SendFlags, Uds};
/// let mut uds = Uds::new(None)?;
///
/// uds.create_network(b"HBW\x10", None, None, b"udsdemo passphrase c186093cd2652741\0", 1)?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "udsCreateNetwork")]
pub fn create_network(
&mut self,
comm_id: &[u8; 4],
additional_id: Option<u8>,
max_nodes: Option<u8>,
passphrase: &[u8],
channel: u8,
) -> crate::Result<()> {
let mut network = MaybeUninit::uninit();
unsafe {
ctru_sys::udsGenerateDefaultNetworkStruct(
network.as_mut_ptr(),
u32::from_be_bytes(*comm_id),
additional_id.unwrap_or(0),
max_nodes.unwrap_or(Self::MAX_NODES).min(Self::MAX_NODES),
)
};
let network = unsafe { network.assume_init() };
let mut context = MaybeUninit::uninit();
ResultCode(unsafe {
ctru_sys::udsCreateNetwork(
&network as *const _,
passphrase.as_ptr().cast(),
passphrase.len(),
context.as_mut_ptr(),
channel,
Self::RECV_BUF_SIZE,
)
})?;
let context = unsafe { context.assume_init() };
self.network.replace(network);
self.context.replace(context);
Ok(())
}
/// Destroy the current 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::{ConnectionType, SendFlags, Uds};
/// let mut uds = Uds::new(None)?;
///
/// uds.create_network(b"HBW\x10", None, None, b"udsdemo passphrase c186093cd2652741\0", 1)?;
/// uds.destroy_network()?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "udsDestroyNetwork")]
pub fn destroy_network(&mut self) -> Result<(), Error> {
if self.service_status() != ServiceStatus::Server {
return Err(Error::NoNetwork);
}
// should always be true
if self.context.is_some() {
self.unbind_context()?;
}
ResultCode(unsafe { ctru_sys::udsDestroyNetwork() })?;
self.network = None;
Ok(())
}
/// Set the app data for the currently hosted network.
///
/// # Errors
///
/// This function will return an error if no network has been created.
/// See [`Uds::create_network()`] to create a network.
/// This function will also return an error if the provided buffer is too large (see [`Uds::MAX_APPDATA_SIZE`]).
///
/// # Example
///
/// ```
/// # let _runner = test_runner::GdbRunner::default();
/// # use std::error::Error;
/// # fn main() -> Result<(), Box<dyn Error>> {
/// #
/// use ctru::services::uds::{ConnectionType, SendFlags, Uds};
/// let mut uds = Uds::new(None)?;
///
/// uds.create_network(b"HBW\x10", None, None, b"udsdemo passphrase c186093cd2652741\0", 1)?;
/// uds.set_appdata(b"Test appdata.\0")?;
/// #
/// # Ok(())
/// # }
/// ```
#[doc(alias = "udsSetApplicationData")]
pub fn set_appdata(&self, data: &[u8]) -> Result<(), Error> {
if self.service_status() != ServiceStatus::Server {
return Err(Error::NoNetwork);
}
if data.len() > Self::MAX_APPDATA_SIZE {
return Err(Error::TooMuchAppData);
}
ResultCode(unsafe { ctru_sys::udsSetApplicationData(data.as_ptr().cast(), data.len()) })?;
Ok(())
}
}
impl Drop for Uds {
#[doc(alias = "udsExit")]
fn drop(&mut self) {
match self.service_status() {
ServiceStatus::Client => self.disconnect_network().unwrap(),
ServiceStatus::Server => self.destroy_network().unwrap(),
_ => {}
};
// ctru_sys::udsExit() is called by the ServiceHandle
}
}
Loading…
Cancel
Save