//! android-ffi:viewer-core 的 JNI 薄层(阶段 1)。 //! //! 线程模型(决定接口形态的原因): //! - Kotlin 解码线程调用 `nativeNextFrame` 同步取帧(内部 block_on,一次一帧); //! - Kotlin UI 线程调用 `nativeSend*` 发输入——tokio unbounded channel 入队不阻塞; //! - 共用一个 current-thread runtime:`block_on(next_frame)` 期间 runtime 仍在驱动 //! QUIC 收发与 Ping;输入入队只是 channel send,不需要 runtime 参与。 //! //! **锁纪律**:`nativeNextFrame` 在等帧期间**不得持有**会话锁——桌面静止时帧间隔 //! 可达 500ms,持锁会让 UI 线程的输入调用同步卡住。做法:短暂持锁克隆 //! `Arc` 与 `&'static Runtime` 引用后立即释放,再 block_on。 //! 代价:block_on 期间 destroy 会 use-after-free——阶段 1 由 Kotlin 侧 //! 「先停解码线程、后 destroy」的顺序纪律保证(登记在方案风险清单)。 //! //! 句柄约定:`nativeConnect` 返回 Box 裸指针(永不为 0)作为 jlong; //! `nativeDestroy` 释放。句柄释放后不得再使用,Kotlin 侧用置 0 保护。 //! //! 帧返回约定:byte[],布局 = [1 标志][8 seq LE][8 ts LE][4 w LE][4 h LE][payload]。 //! 定长头 25 字节;Kotlin 读 buf[0] 判断 keyframe(标志位复用为:0=P 帧, 1=IDR 帧, //! 会话结束则整个返回 null)。 use common::InputEvent; use jni::objects::{JCharArray, JClass, JObject, JString}; use jni::sys::{jbyteArray, jint, jlong, jstring}; use jni::JNIEnv; use std::sync::{Arc, Mutex}; use viewer_core::{ConnectConfig, Session}; /// 帧头长度:keyframe 标志(1) + seq(8) + ts(8) + w(4) + h(4) const FRAME_HEADER_LEN: usize = 25; struct SessionBox { session: Arc, runtime: tokio::runtime::Runtime, } /// 连接被控端。成功返回句柄(>0),失败返回 0;错误消息经 `nativeLastError()` 取, /// **错误分类**经 `nativeLastErrorKind()` 取(3a:界面按分类显示中文文案,原始英文作第二行)。 #[no_mangle] pub extern "system" fn Java_com_verify_remote_RustBridge_nativeConnect<'local>( mut env: JNIEnv<'local>, _class: JClass<'local>, host: JString<'local>, port: jint, code: JString<'local>, ) -> jlong { let host_s = match env.get_string(&host) { Ok(s) => s.to_string_lossy().into_owned(), Err(_) => { set_last_error(-1, "host 参数不是合法字符串"); return 0; } }; let code_s = match env.get_string(&code) { Ok(s) => s.to_string_lossy().into_owned(), Err(_) => { set_last_error(-1, "code 参数不是合法字符串"); return 0; } }; let built = std::panic::catch_unwind(|| { let runtime = match tokio::runtime::Builder::new_current_thread() .enable_all() .build() { Ok(r) => r, Err(e) => return Err((-1i32, format!("runtime init: {e}"))), }; let cfg = ConnectConfig::direct(host_s, port as u16, code_s); match runtime.block_on(Session::connect(cfg)) { Ok(session) => Ok(SessionBox { session: Arc::new(session), runtime, }), // ConnectError 的 Display 即原始 detail;kind.code() 与 // viewer_core::ConnectErrorKind::code 的映射表一致(FFI 契约)。 Err(e) => Err((e.kind.code(), e.detail)), } }); match built { Ok(Ok(boxed)) => { set_last_error(0, ""); Box::into_raw(Box::new(Mutex::new(boxed))) as jlong } Ok(Err((kind, msg))) => { set_last_error(kind, &msg); 0 } Err(_) => { set_last_error(-1, "panic in nativeConnect"); 0 } } } /// kind = 0 表示清除;-1 表示 FFI 层自身错误(无 viewer-core 分类,Kotlin 走 Other 文案)。 fn set_last_error(kind: i32, msg: &str) { let mut slot = LAST_ERROR.lock().unwrap(); if msg.is_empty() { *slot = None; } else { *slot = Some((kind, msg.to_string())); } } static LAST_ERROR: Mutex> = Mutex::new(None); /// 取最近一次 nativeConnect 的错误消息(无错误返回 null)。 /// FFI 契约:调用方必须先 `nativeLastErrorKind()` 再 `nativeLastError()`—— /// 本函数读走消息时**清空整个槽位**(kind 一起失效),颠倒顺序会拿到 kind=0。 #[no_mangle] pub extern "system" fn Java_com_verify_remote_RustBridge_nativeLastError<'local>( env: JNIEnv<'local>, _class: JClass<'local>, ) -> jstring { let mut slot = LAST_ERROR.lock().unwrap(); let msg = slot.as_ref().map(|(_, m)| m.clone()); match msg { Some(m) => { *slot = None; env.new_string(m) .map(|s| s.into_raw()) .unwrap_or(std::ptr::null_mut()) } None => std::ptr::null_mut(), } } /// 取最近一次 nativeConnect 的错误分类码(viewer_core::ConnectErrorKind::code)。 /// 只读不清空;0 = 无错误,-1 = FFI 层自身错误(参数非法/panic/runtime 初始化失败, /// 无 viewer-core 分类,Kotlin 按 Other 文案 + 原始消息显示)。 #[no_mangle] pub extern "system" fn Java_com_verify_remote_RustBridge_nativeLastErrorKind( _env: JNIEnv, _class: JClass, ) -> jint { let slot = LAST_ERROR.lock().unwrap(); slot.as_ref().map(|(k, _)| *k).unwrap_or(0) } /// 取下一帧(阻塞直到有帧或会话结束)。返回 null = 会话结束。 /// 仅允许解码线程调用;destroy 前必须先停止本线程(见文件头锁纪律)。 #[no_mangle] pub extern "system" fn Java_com_verify_remote_RustBridge_nativeNextFrame<'local>( env: JNIEnv<'local>, _class: JClass<'local>, handle: jlong, ) -> jbyteArray { // 短暂持锁:克隆 Arc 与拿 runtime 引用,随后立即释放——等帧不能挡输入。 let (session, runtime) = { let Some(boxed) = handle_to_box(handle) else { return std::ptr::null_mut(); }; let guard = lock_box(boxed); (guard.session.clone(), &guard.runtime as *const tokio::runtime::Runtime) }; // SAFETY: 见文件头「锁纪律」。Kotlin 保证解码线程存活期间不 destroy。 let rt = unsafe { &*runtime }; match rt.block_on(session.next_frame()) { None => std::ptr::null_mut(), Some(f) => { let mut buf = vec![0u8; FRAME_HEADER_LEN + f.payload.len()]; buf[0] = f.keyframe as u8; buf[1..9].copy_from_slice(&f.seq.to_le_bytes()); buf[9..17].copy_from_slice(&f.ts_ms.to_le_bytes()); buf[17..21].copy_from_slice(&f.width.to_le_bytes()); buf[21..25].copy_from_slice(&f.height.to_le_bytes()); buf[FRAME_HEADER_LEN..].copy_from_slice(&f.payload); match env.byte_array_from_slice(&buf) { Ok(a) => a.into_raw(), Err(_) => std::ptr::null_mut(), } } } } /// 会话统计打包:高 32 位 rtt_ms,低 32 位 frames_in 低 32 位。-1 = 句柄无效。 #[no_mangle] pub extern "system" fn Java_com_verify_remote_RustBridge_nativeStats( _env: JNIEnv, _class: JClass, handle: jlong, ) -> jlong { let Some(boxed) = handle_to_box(handle) else { return -1; }; let guard = lock_box(boxed); let st = guard.session.stats(); // 打包进 i64:高 32 位 rtt_ms、低 32 位 frames_in,Kotlin 侧移位还原 (((st.rtt_ms & 0xFFFF_FFFF) << 32) | (st.frames_in & 0xFFFF_FFFF)) as jlong } /// 被控端桌面尺寸,打包成 i64:高 32 位宽、低 32 位高。0 = 句柄无效。 /// Kotlin 侧必须在建解码器之前调用,用返回值定 SurfaceView 宽高比。 #[no_mangle] pub extern "system" fn Java_com_verify_remote_RustBridge_nativeDesktopSize( _env: JNIEnv, _class: JClass, handle: jlong, ) -> jlong { let Some(boxed) = handle_to_box(handle) else { return 0; }; let guard = lock_box(boxed); let (w, h) = guard.session.desktop_size(); (((w as u64 & 0xFFFF_FFFF) << 32) | (h as u64 & 0xFFFF_FFFF)) as jlong } #[no_mangle] pub extern "system" fn Java_com_verify_remote_RustBridge_nativeSendMouseMove( _env: JNIEnv, _class: JClass, handle: jlong, x: jint, y: jint, ) { with_session(handle, |s| { s.send_input(InputEvent::MouseMove { x: x.clamp(0, 65535) as u16, y: y.clamp(0, 65535) as u16, }) }); } /// button: 0=左 1=右 其他=中;down: 1=按下 0=抬起 #[no_mangle] pub extern "system" fn Java_com_verify_remote_RustBridge_nativeSendMouseButton( _env: JNIEnv, _class: JClass, handle: jlong, button: jint, down: jint, ) { use common::MouseButton; let b = match button { 0 => MouseButton::Left, 1 => MouseButton::Right, _ => MouseButton::Middle, }; with_session(handle, |s| { s.send_input(if down != 0 { InputEvent::MouseDown(b) } else { InputEvent::MouseUp(b) }) }); } #[no_mangle] pub extern "system" fn Java_com_verify_remote_RustBridge_nativeSendWheel( _env: JNIEnv, _class: JClass, handle: jlong, dy: jint, ) { with_session(handle, |s| s.send_input(InputEvent::Wheel { dy })); } #[no_mangle] pub extern "system" fn Java_com_verify_remote_RustBridge_nativeSendKey( _env: JNIEnv, _class: JClass, handle: jlong, scancode: jint, extended: jint, down: jint, ) { with_session(handle, |s| { s.send_input(if down != 0 { InputEvent::KeyDown { scancode: scancode as u16, extended: extended != 0, } } else { InputEvent::KeyUp { scancode: scancode as u16, extended: extended != 0, } }) }); } /// 协议 v3:文本上行。`units` 是 Kotlin 侧的 `CharArray`(UTF-16 code unit)。 /// /// 为什么不用 `JString`:jni 0.21 的 `get_string` 走 Modified-UTF-8 → Rust `String`, /// 落单代理项(IME 提交边界常见)会在这次往返里丢失。`char[]` 经 /// `GetCharArrayRegion` 按 u16 直接拷贝,零转换(`jchar = u16`,见 jni-sys-0.3.1/lib.rs:15)。 /// /// 截断与空输入的判断在 `viewer_core::Session::send_text` 里做,与被控端同一口径。 /// 失败必须有可见后果:JNI 取数失败走 eprintln(Android 上落 stderr→logcat), /// 不静默返回成功——用户对着远端打字却没字,最坏的就是「看起来发送了」。 #[no_mangle] pub extern "system" fn Java_com_verify_remote_RustBridge_nativeSendText<'local>( // 不需要 `mut`:jni 0.21.1 的 `get_array_length` / `get_char_array_region` // 都是 `&self`(jnienv.rs:1652 / :1887),与 `new_string` 之类的 `&mut self` 不同。 env: JNIEnv<'local>, _class: JClass<'local>, handle: jlong, units: JObject<'local>, ) { if units.as_raw().is_null() { // Kotlin 侧不会传 null,但 JNI 入口的对端数据一律不信任(全局安全规则) return; } let arr = JCharArray::from(units); let len = match env.get_array_length(&arr) { Ok(n) if n > 0 => n as usize, Ok(_) => return, Err(e) => { eprintln!("[android-ffi] get_array_length failed: {e}"); return; } }; let mut buf = vec![0u16; len]; if let Err(e) = env.get_char_array_region(&arr, 0, &mut buf) { eprintln!("[android-ffi] get_char_array_region failed: {e}"); return; } with_session(handle, |s| s.send_text(&buf)); } #[no_mangle] pub extern "system" fn Java_com_verify_remote_RustBridge_nativeRequestKeyframe( _env: JNIEnv, _class: JClass, handle: jlong, ) { with_session(handle, |s| s.request_keyframe()); } /// 释放会话。Kotlin 侧调用后必须把句柄置 0,且先停解码线程。 #[no_mangle] pub extern "system" fn Java_com_verify_remote_RustBridge_nativeDestroy( _env: JNIEnv, _class: JClass, handle: jlong, ) { if handle == 0 { return; } let real = handle as *mut Mutex; unsafe { let _ = Box::from_raw(real); } } fn handle_to_box(handle: jlong) -> Option<&'static Mutex> { if handle == 0 { return None; } let real = handle as *mut Mutex; // 悬垂指针无法在 Rust 侧检测:阶段 1 由 Kotlin「destroy 后置 0 + 先停线程」 // 的顺序纪律保证。违反即 UB,登记在方案风险清单(阶段 2 可加代际表消除)。 // NonNull::new 是安全函数;解引用才是 unsafe 点 std::ptr::NonNull::new(real).map(|p| unsafe { p.as_ref() }) } /// 锁中毒容忍:SessionBox 内部无跨线程共享可变状态(Session 各方法内部自带 /// 并发原语),中毒后继续用是安全的。 fn lock_box(b: &Mutex) -> std::sync::MutexGuard<'_, SessionBox> { match b.lock() { Ok(g) => g, Err(p) => p.into_inner(), } } fn with_session(handle: jlong, f: impl FnOnce(&Session)) { let Some(boxed) = handle_to_box(handle) else { return; }; let guard = lock_box(boxed); f(&guard.session); }