Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement UDS service with example #156

Merged
merged 14 commits into from
Apr 9, 2024
Merged
1 change: 1 addition & 0 deletions ctru-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
301 changes: 301 additions & 0 deletions ctru-rs/examples/local-networking.rs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, this is a pretty intense example but it seems necessary given how many features there are in the uds service. I was wondering if some of this functionality could go onto Uds itself instead of just in this example, but honestly on first read I don't see any obvious things that make sense, so 👍 good with me

Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
//! Local networking example.
//!
//! This example showcases local networking using the UDS module.

use ctru::prelude::*;
use ctru::services::uds::*;

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();
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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks a lot like a manually implemented future state machine. could this have worked better with async?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very possibly! I'm scared of async.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've done some basic reading about futures, and I don't understand how this could be done with async. Could you explain how that would work? It would definitely be nice to clean up the code if it's possible.

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;

let mut prev_node_mask = 0;

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();
prev_node_mask = 0;
} else if hid.keys_down().contains(KeyPad::B) {
state = State::Create;
console.clear();
prev_node_mask = 0;
}
}
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)?;
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()?;
println!("Connected using channel {}", channel);

let appdata = uds.get_appdata(None)?;
println!("App data: {:02X?}", appdata);

if uds.wait_status_event(false, false)? {
prev_node_mask = handle_status_event(&uds, prev_node_mask)?;
}

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 {:?}",
pkt[0], pkt[1], pkt[2], pkt[3], node
);
}

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()?;
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(),
NodeID::Broadcast,
data_channel,
SendFlags::Default,
)?;
}
}
}
Err(e) => {
uds.disconnect_network()?;
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)?;

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 {:?}",
pkt[0], pkt[1], pkt[2], pkt[3], node
);
}

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()?;
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(),
NodeID::Broadcast,
data_channel,
SendFlags::Default,
)?;
}
}
Err(e) => {
uds.destroy_network()?;
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");
}
}
}
}
}

Ok(())
}
1 change: 1 addition & 0 deletions ctru-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion ctru-rs/src/services/ir_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ impl IrUser {
shared_mem.shared_memory_layout,
);

Ok(())
Ok::<_, Error>(())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AzureMarker Ehm, do you remember why a closure is used here? It doesn't seem to be necessary.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was done so I could use ?, like the unstable try block.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change was the easiest way to fix a compile error introduced by my implementation of FromResidual. If I handle errors a different way, this change can be reverted.

})()
.unwrap();
},
Expand Down
1 change: 1 addition & 0 deletions ctru-rs/src/services/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))] {
Expand Down
Loading