Jhynjhiruu
11 months ago
6 changed files with 1246 additions and 1 deletions
@ -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"); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -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…
Reference in new issue