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

Static init of the PS service #49

Merged
merged 3 commits into from
Feb 24, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions ctru-rs/examples/futures-tokio.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use ctru::console::Console;
use ctru::services::hid::KeyPad;
use ctru::services::ps::Ps;
use ctru::services::{Apt, Hid};
use ctru::Gfx;
use std::time::Duration;
Expand All @@ -10,7 +9,6 @@ fn main() {
let gfx = Gfx::default();
let hid = Hid::init().expect("Couldn't obtain HID controller");
let apt = Apt::init().expect("Couldn't obtain APT controller");
let _ps = Ps::init().expect("Couldn't initialize PS service");
let _console = Console::init(gfx.top_screen.borrow_mut());

// Give ourselves up to 30% of the system core's time
Expand Down
12 changes: 4 additions & 8 deletions ctru-rs/examples/hashmaps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,19 @@ use ctru::console::Console;
use ctru::gfx::Gfx;
use ctru::services::apt::Apt;
use ctru::services::hid::{Hid, KeyPad};
use ctru::services::ps::Ps;

fn main() {
// Initialize services
//
// HashMaps generate hashes thanks to the 3DS' criptografically secure generator.
Meziu marked this conversation as resolved.
Show resolved Hide resolved
// This generator is only active when activating the `PS` service.
// This service is automatically initialized in `ctru::init`
ctru::init();
let apt = Apt::init().unwrap();
let hid = Hid::init().unwrap();
let gfx = Gfx::default();
let _console = Console::init(gfx.top_screen.borrow_mut());

// HashMaps generate hashes thanks to the 3DS' criptografically secure generator.
// Sadly, this generator is only active when activating the `Ps` service.
// To do this, we have to make sure the `Ps` service handle is alive for the whole
// run time (or at least, when `HashMaps` are used).
// Not having a living `Ps` instance when using `HashMap`s results in a panic
let _ps = Ps::init().unwrap();

let mut map = std::collections::HashMap::new();
map.insert("A Key!", 102);
map.insert("Another key?", 543);
Expand Down
14 changes: 14 additions & 0 deletions ctru-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
#![feature(custom_test_frameworks)]
#![test_runner(test_runner::run)]

extern "C" fn services_deinit() {
unsafe {
ctru_sys::psExit();
}
}

/// Call this somewhere to force Rust to link some required crates
/// This is also a setup for some crate integration only available at runtime
///
Expand All @@ -12,6 +18,14 @@ pub fn init() {
linker_fix_3ds::init();
pthread_3ds::init();

// Initialize the PS service for random data generation
unsafe {
ctru_sys::psInit();

// Setup the deconstruction at the program's end
libc::atexit(services_deinit);
}

use std::panic::PanicInfo;

let main_thread = thread::current().id();
Expand Down
101 changes: 22 additions & 79 deletions ctru-rs/src/services/ps.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
//! Process Services (PS) module. This is used for miscellaneous utility tasks, but
//! is particularly important because it is used to generate random data, which
//! is required for common things like [`HashMap`](std::collections::HashMap).
//! As such, it is initialized by default in `ctru::init` instead of having a safety handler
//! See also <https://www.3dbrew.org/wiki/Process_Services>

/// PS handle. This must not be dropped in order for random generation
/// to work (in most cases, the lifetime of an application).
#[non_exhaustive]
pub struct Ps;

#[repr(u32)]
pub enum AESAlgorithm {
CbcEnc,
Expand All @@ -32,55 +28,34 @@ pub enum AESKeyType {
Keyslot39Nfc,
}

impl Ps {
/// Initialize the PS module.
pub fn init() -> crate::Result<Self> {
let r = unsafe { ctru_sys::psInit() };
if r < 0 {
Err(r.into())
} else {
Ok(Self)
}
}

pub fn local_friend_code_seed(&self) -> crate::Result<u64> {
let mut seed: u64 = 0;
pub fn local_friend_code_seed() -> crate::Result<u64> {
let mut seed: u64 = 0;

let r = unsafe { ctru_sys::PS_GetLocalFriendCodeSeed(&mut seed) };
if r < 0 {
Err(r.into())
} else {
Ok(seed)
}
let r = unsafe { ctru_sys::PS_GetLocalFriendCodeSeed(&mut seed) };
if r < 0 {
Err(r.into())
} else {
Ok(seed)
}
}

pub fn device_id(&self) -> crate::Result<u32> {
let mut id: u32 = 0;

let r = unsafe { ctru_sys::PS_GetDeviceId(&mut id) };
if r < 0 {
Err(r.into())
} else {
Ok(id)
}
}
pub fn device_id() -> crate::Result<u32> {
let mut id: u32 = 0;

pub fn generate_random_bytes(&self, out: &mut [u8]) -> crate::Result<()> {
let r =
unsafe { ctru_sys::PS_GenerateRandomBytes(out as *mut _ as *mut _, out.len() as u32) };
if r < 0 {
Err(r.into())
} else {
Ok(())
}
let r = unsafe { ctru_sys::PS_GetDeviceId(&mut id) };
if r < 0 {
Err(r.into())
} else {
Ok(id)
}
}

impl Drop for Ps {
fn drop(&mut self) {
unsafe {
ctru_sys::psExit();
}
pub fn generate_random_bytes(out: &mut [u8]) -> crate::Result<()> {
let r = unsafe { ctru_sys::PS_GenerateRandomBytes(out as *mut _ as *mut _, out.len() as u32) };
if r < 0 {
Err(r.into())
} else {
Ok(())
}
}

Expand All @@ -92,8 +67,6 @@ mod tests {

#[test]
fn construct_hash_map() {
let _ps = Ps::init().unwrap();

let mut input = vec![
(1_i32, String::from("123")),
(2, String::from("2")),
Expand All @@ -108,34 +81,4 @@ mod tests {

assert_eq!(input, actual);
}

#[test]
fn construct_hash_map_no_rand() {
// Without initializing PS, we can't use `libc::getrandom` and constructing
// a HashMap panics at runtime.
//
// If any test case successfully creates a HashMap before this test,
// the thread-local RandomState in std will be initialized. We spawn
// a new thread to actually create the hash map, since even in multi-threaded
// test environment there's a chance this test wouldn't panic because
// some other test case ran before it.
//
// One downside of this approach is that the panic handler for the panicking
// thread prints to the console, which is not captured by the default test
// harness and prints even when the test passes.
crate::thread::Builder::new()
.stack_size(0x20_0000)
.spawn(|| {
let map: HashMap<i32, String> = HashMap::from_iter([
(1_i32, String::from("123")),
(2, String::from("2")),
(6, String::from("six")),
]);

dbg!(map);
})
.unwrap()
.join()
.expect_err("should have panicked");
}
}