uniffi::setup_scaffolding!(); mod backdrop; pub mod scenes; use std::path::Path; // A simple test function to ensure the Kotlin <-> Rust bridge is working. #[uniffi::export] pub fn oxygen_greeting(name: String) -> String { format!("Hello, {}! Oxygen Native Core is active.", name) } /// Why a runner call failed, in OxygenFlow's words. #[derive(Debug, uniffi::Error)] pub enum RunnerError { Failed { reason: String }, } impl std::fmt::Display for RunnerError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { RunnerError::Failed { reason } => f.write_str(reason), } } } impl std::error::Error for RunnerError {} fn failed(e: anyhow::Error) -> RunnerError { RunnerError::Failed { reason: format!("{e:#}") } } /// The phone as an OxygenFlow runner (oxygenhub-android#8): OxygenFlow's /// own runner, built into the app, offering only the WASM tier. `home` is /// the app's directory for it; `register` keeps the runner's credential /// there (runner.toml), as `flow runner register` does on a host. #[uniffi::export] pub fn runner_register(home: String, hub: String, token: String, name: String) -> Result { let h = flow::home::Home::locate(Some(Path::new(&home))).map_err(failed)?; let r = flow::runner::register(&h, &hub, &token, &name, None, vec!["android".into()]).map_err(failed)?; Ok(r.name) } /// Whether `home` holds a registration, and so can serve. #[uniffi::export] pub fn runner_is_registered(home: String) -> bool { Path::new(&home).join(flow::home::RUNNER_TOML).is_file() } /// Claim and run jobs until [`runner_stop`] drains the runner: returns /// once the jobs it holds are done and reported. Blocks, so call it from /// a thread of its own (the app's runner service). #[uniffi::export] pub fn runner_serve(home: String) -> Result<(), RunnerError> { let h = flow::home::Home::locate(Some(Path::new(&home))).map_err(failed)?; flow::runner::serve_embedded(&h).map_err(failed) } /// Drain the runner: claim nothing more, finish and report what it /// holds, then [`runner_serve`] returns. For leaving the charger or the /// allowed network, or the owner stopping it. #[uniffi::export] pub fn runner_stop() { flow::runner::stop(); } /// Leave (oxygenflow#76): claim nothing more, and hand the jobs it holds /// back to the hub, which gives them to another runner ahead of new work; /// [`runner_serve`] then returns. With `notice_secs`, a job the hub expects /// to finish within the notice finishes first (its checkpoint saved if /// not). 0 for leaving the allowed network, where finishing would use the /// network the owner ruled out; a minute for leaving the charger. #[uniffi::export] pub fn runner_leave(reason: String, notice_secs: u64) { flow::runner::leave_within(&reason, std::time::Duration::from_secs(notice_secs)); } /// The tiers this phone offers (the WASM tier), and why not the others. #[uniffi::export] pub fn runner_tiers(home: String) -> Vec { let Ok(h) = flow::home::Home::locate(Some(Path::new(&home))) else { return vec![] }; match flow::runner::tiers(&h) { Ok((have, _)) => have, Err(_) => vec![], } } /// A new X25519 secret for this phone's alerts (#16), to keep wrapped by /// the Android Keystore. #[uniffi::export] pub fn alert_new_secret() -> Vec { flow_core::alert::new_secret().to_vec() } /// The public key of an alert secret, for the hub; empty for a secret that /// is not 32 bytes. #[uniffi::export] pub fn alert_public_key(secret: Vec) -> Vec { <[u8; 32]>::try_from(secret.as_slice()).map(|s| flow_core::alert::public_key(&s).to_vec()).unwrap_or_default() } /// Open an alert the hub sealed to this phone; `None` when it was not /// sealed to `secret` or was changed on the way. #[uniffi::export] pub fn alert_open(secret: Vec, sealed: Vec) -> Option> { let s = <[u8; 32]>::try_from(secret.as_slice()).ok()?; flow_core::alert::open(&s, &sealed) }