//! Handing the Android runtime to rustls-platform-verifier. //! //! Every TLS client in the app -- reqwest for HTTP, async-imap for mail fetch, //! the updater -- validates against the OS trust store through //! `rustls-platform-verifier`. On every other platform that crate reads the //! trust store directly and needs no setup. On Android the trust store is only //! reachable through the JVM, so the crate has to be handed a `JavaVM`, an //! application `Context` and a class loader before the first handshake, and it //! `expect()`s on that state rather than falling back. Skip this and the app //! launches, works offline, and panics the moment anything reaches the network. //! //! The handles arrive from Kotlin: `MainActivity.onCreate` calls the native //! method below *before* `super.onCreate()`, because Tauri starts the Rust app //! from `WryLifecycleObserver.onCreate` inside that super call and sync can be //! running by the time it returns. //! //! Note this is jni 0.22, which `rustls-platform-verifier` 0.7 depends on -- //! not the 0.21 that wry and tauri use. Both are in the graph on purpose and //! must not be "unified": the types here have to match the crate being //! initialised, so this module follows rustls-platform-verifier's jni version //! and nothing else. use std::sync::OnceLock; use jni::EnvUnowned; use jni::objects::JObject; /// Set when initialisation fails, drained by [`report_init_failure`]. /// /// This exists because of an ordering trap. The native method below runs from /// `MainActivity.onCreate` *before* `super.onCreate()`, which is what makes it /// early enough to be useful -- but `tracing_subscriber::fmt::init()` runs /// inside that same super call, from the mobile entry point in `lib.rs`. Logging /// the failure where it happens would therefore write to a subscriber that does /// not exist yet and be dropped, leaving the one diagnostic for "no TLS at all" /// invisible. So the error is parked here and logged once there is somewhere for /// it to go. static INIT_FAILURE: OnceLock = OnceLock::new(); /// `MainActivity.initRustlsPlatformVerifier(Context)`. /// /// Takes the *application* context rather than the activity: the crate holds a /// global reference to whatever it is given for the life of the process, and /// pinning an Activity there would leak it across every rotation and fold. /// /// A failure is recorded rather than thrown. GoingsOn is local-first and a /// tasks-and-calendar session is entirely usable with no network, so taking the /// whole app down at startup would cost more than it explains. The tradeoff is /// that a failure surfaces later as a panic on first network use, which is why /// [`report_init_failure`] exists to name the real cause before that happens. #[unsafe(no_mangle)] pub extern "system" fn Java_com_goingson_app_MainActivity_initRustlsPlatformVerifier<'local>( mut env: EnvUnowned<'local>, _this: JObject<'local>, context: JObject<'local>, ) { env.with_env(|env| { if let Err(e) = rustls_platform_verifier::android::init_with_env(env, context) { let _ = INIT_FAILURE.set(e.to_string()); } Ok::<(), jni::errors::Error>(()) }) .resolve::(); } /// Logs an initialisation failure, if there was one. /// /// Call once from the mobile entry point, after the tracing subscriber is /// installed and before anything can reach the network. pub fn report_init_failure() { if let Some(e) = INIT_FAILURE.get() { tracing::error!( error = %e, "rustls-platform-verifier did not initialise: no TLS connection can be \ verified, so sync and mail will panic rather than fall back. Restarting \ the app is the only recovery." ); } }