Skip to main content

max / goingson

3.7 KB · 80 lines History Blame Raw
1 //! Handing the Android runtime to rustls-platform-verifier.
2 //!
3 //! Every TLS client in the app -- reqwest for HTTP, async-imap for mail fetch,
4 //! the updater -- validates against the OS trust store through
5 //! `rustls-platform-verifier`. On every other platform that crate reads the
6 //! trust store directly and needs no setup. On Android the trust store is only
7 //! reachable through the JVM, so the crate has to be handed a `JavaVM`, an
8 //! application `Context` and a class loader before the first handshake, and it
9 //! `expect()`s on that state rather than falling back. Skip this and the app
10 //! launches, works offline, and panics the moment anything reaches the network.
11 //!
12 //! The handles arrive from Kotlin: `MainActivity.onCreate` calls the native
13 //! method below *before* `super.onCreate()`, because Tauri starts the Rust app
14 //! from `WryLifecycleObserver.onCreate` inside that super call and sync can be
15 //! running by the time it returns.
16 //!
17 //! Note this is jni 0.22, which `rustls-platform-verifier` 0.7 depends on --
18 //! not the 0.21 that wry and tauri use. Both are in the graph on purpose and
19 //! must not be "unified": the types here have to match the crate being
20 //! initialised, so this module follows rustls-platform-verifier's jni version
21 //! and nothing else.
22
23 use std::sync::OnceLock;
24
25 use jni::EnvUnowned;
26 use jni::objects::JObject;
27
28 /// Set when initialisation fails, drained by [`report_init_failure`].
29 ///
30 /// This exists because of an ordering trap. The native method below runs from
31 /// `MainActivity.onCreate` *before* `super.onCreate()`, which is what makes it
32 /// early enough to be useful -- but `tracing_subscriber::fmt::init()` runs
33 /// inside that same super call, from the mobile entry point in `lib.rs`. Logging
34 /// the failure where it happens would therefore write to a subscriber that does
35 /// not exist yet and be dropped, leaving the one diagnostic for "no TLS at all"
36 /// invisible. So the error is parked here and logged once there is somewhere for
37 /// it to go.
38 static INIT_FAILURE: OnceLock<String> = OnceLock::new();
39
40 /// `MainActivity.initRustlsPlatformVerifier(Context)`.
41 ///
42 /// Takes the *application* context rather than the activity: the crate holds a
43 /// global reference to whatever it is given for the life of the process, and
44 /// pinning an Activity there would leak it across every rotation and fold.
45 ///
46 /// A failure is recorded rather than thrown. GoingsOn is local-first and a
47 /// tasks-and-calendar session is entirely usable with no network, so taking the
48 /// whole app down at startup would cost more than it explains. The tradeoff is
49 /// that a failure surfaces later as a panic on first network use, which is why
50 /// [`report_init_failure`] exists to name the real cause before that happens.
51 #[unsafe(no_mangle)]
52 pub extern "system" fn Java_com_goingson_app_MainActivity_initRustlsPlatformVerifier<'local>(
53 mut env: EnvUnowned<'local>,
54 _this: JObject<'local>,
55 context: JObject<'local>,
56 ) {
57 env.with_env(|env| {
58 if let Err(e) = rustls_platform_verifier::android::init_with_env(env, context) {
59 let _ = INIT_FAILURE.set(e.to_string());
60 }
61 Ok::<(), jni::errors::Error>(())
62 })
63 .resolve::<jni::errors::LogErrorAndDefault>();
64 }
65
66 /// Logs an initialisation failure, if there was one.
67 ///
68 /// Call once from the mobile entry point, after the tracing subscriber is
69 /// installed and before anything can reach the network.
70 pub fn report_init_failure() {
71 if let Some(e) = INIT_FAILURE.get() {
72 tracing::error!(
73 error = %e,
74 "rustls-platform-verifier did not initialise: no TLS connection can be \
75 verified, so sync and mail will panic rather than fall back. Restarting \
76 the app is the only recovery."
77 );
78 }
79 }
80