fix: ping as engine instance method — same lifecycle as call
Some checks failed
Mirror to GitHub / mirror (push) Failing after 7s
Build Release Binaries / build-amd64 (push) Failing after 19s

Ping was a static JNI method that loaded the .so before nativeInit,
crashing jemalloc. Now ping is an instance method on WzpEngine:

- Engine is created once (nativeInit), reused for both ping and call
- pingRelay() uses same tokio runtime pattern as startCall()
- Auto-pings all servers on app launch (after engine init)
- No process restart needed
- TOFU fingerprints saved on first successful ping

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-04-07 09:49:33 +04:00
parent eeb85aeac2
commit 18f7faa279
7 changed files with 303 additions and 150 deletions

View File

@@ -169,6 +169,46 @@ impl WzpEngine {
info!("stop_call: done");
}
/// Ping a relay — same pattern as start_call (creates runtime on calling thread).
/// Returns JSON `{"rtt_ms":N,"server_fingerprint":"hex"}` or error.
pub fn ping_relay(&self, address: &str) -> Result<String, anyhow::Error> {
let addr: SocketAddr = address.parse()?;
let _ = rustls::crypto::ring::default_provider().install_default();
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?;
rt.block_on(async {
let bind: SocketAddr = "0.0.0.0:0".parse().unwrap();
let endpoint = wzp_transport::create_endpoint(bind, None)?;
let client_cfg = wzp_transport::client_config();
let start = Instant::now();
let conn = tokio::time::timeout(
std::time::Duration::from_secs(3),
wzp_transport::connect(&endpoint, addr, "ping", client_cfg),
)
.await
.map_err(|_| anyhow::anyhow!("timeout"))??;
let rtt_ms = start.elapsed().as_millis() as u64;
let server_fp = conn
.peer_identity()
.and_then(|id| id.downcast::<Vec<rustls::pki_types::CertificateDer>>().ok())
.and_then(|certs| certs.first().map(|c| {
use std::hash::{Hash, Hasher};
let mut h = std::collections::hash_map::DefaultHasher::new();
c.as_ref().hash(&mut h);
format!("{:016x}", h.finish())
}))
.unwrap_or_default();
conn.close(0u32.into(), b"ping");
Ok(format!(r#"{{"rtt_ms":{},"server_fingerprint":"{}"}}"#, rtt_ms, server_fp))
})
}
pub fn set_mute(&self, muted: bool) {
self.state.muted.store(muted, Ordering::Relaxed);
}

View File

@@ -318,71 +318,22 @@ pub unsafe extern "system" fn Java_com_wzp_engine_WzpEngine_nativeDestroy(
}));
}
/// Ping a relay server — returns JSON `{"rtt_ms":N,"server_fingerprint":"hex"}` or null on failure.
/// Does NOT require an engine handle — creates a temporary QUIC connection.
/// Ping a relay server — instance method, requires engine handle.
/// Returns JSON `{"rtt_ms":N,"server_fingerprint":"hex"}` or null on failure.
#[unsafe(no_mangle)]
pub unsafe extern "system" fn Java_com_wzp_engine_WzpEngine_nativePingRelay<'a>(
mut env: JNIEnv<'a>,
_class: JClass,
handle: jlong,
relay_j: JString,
) -> jstring {
let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
let h = unsafe { handle_ref(handle) };
let relay: String = env.get_string(&relay_j).map(|s| s.into()).unwrap_or_default();
let addr: std::net::SocketAddr = match relay.parse() {
Ok(a) => a,
Err(_) => return None,
};
let _ = rustls::crypto::ring::default_provider().install_default();
let rt = match tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
{
Ok(rt) => rt,
Err(_) => return None,
};
rt.block_on(async {
let bind: std::net::SocketAddr = "0.0.0.0:0".parse().unwrap();
let endpoint = match wzp_transport::create_endpoint(bind, None) {
Ok(e) => e,
Err(_) => return None,
};
let client_cfg = wzp_transport::client_config();
let start = std::time::Instant::now();
match tokio::time::timeout(
std::time::Duration::from_secs(3),
wzp_transport::connect(&endpoint, addr, "ping", client_cfg),
)
.await
{
Ok(Ok(conn)) => {
let rtt_ms = start.elapsed().as_millis() as u64;
let server_fp = conn
.peer_identity()
.and_then(|id| {
id.downcast::<Vec<rustls::pki_types::CertificateDer>>().ok()
})
.and_then(|certs| {
certs.first().map(|c| {
use std::hash::{Hash, Hasher};
let mut h = std::collections::hash_map::DefaultHasher::new();
c.as_ref().hash(&mut h);
format!("{:016x}", h.finish())
})
})
.unwrap_or_default();
conn.close(0u32.into(), b"ping");
Some(format!(
r#"{{"rtt_ms":{},"server_fingerprint":"{}"}}"#,
rtt_ms, server_fp
))
}
_ => None,
}
})
match h.engine.ping_relay(&relay) {
Ok(json) => Some(json),
Err(_) => None,
}
}));
let json = match result {