From 455b104a81b7c9264558a006277907902e6df81a Mon Sep 17 00:00:00 2001 From: Igor Zaslavsky Date: Tue, 29 Aug 2023 00:42:54 +0000 Subject: [PATCH] Implement Discovery PLATFORM logic in Rust This commit introduces new PAL that is used by ReamoteAuth protocol communicate with remote device Design doc: go/remote-auth-manager-fishfood-design Test: built successfully. Bug: : 291333048 Change-Id: I9c78137ccf2fd6c86735bef0dd0c9cedf589d704 --- remoteauth/service/Android.bp | 2 +- remoteauth/service/jni/Android.bp | 76 +++++ .../remoteauth_rust_test_config_template.xml | 32 ++ remoteauth/service/jni/src/jnames.rs | 17 + remoteauth/service/jni/src/lib.rs | 24 ++ .../src/remoteauth_jni_android_platform.rs | 306 ++++++++++++++++++ remoteauth/service/jni/src/unique_jvm.rs | 48 +++ 7 files changed, 504 insertions(+), 1 deletion(-) create mode 100644 remoteauth/service/jni/Android.bp create mode 100644 remoteauth/service/jni/remoteauth_rust_test_config_template.xml create mode 100644 remoteauth/service/jni/src/jnames.rs create mode 100644 remoteauth/service/jni/src/lib.rs create mode 100644 remoteauth/service/jni/src/remoteauth_jni_android_platform.rs create mode 100644 remoteauth/service/jni/src/unique_jvm.rs diff --git a/remoteauth/service/Android.bp b/remoteauth/service/Android.bp index c3a9fb3045..f42388a25d 100644 --- a/remoteauth/service/Android.bp +++ b/remoteauth/service/Android.bp @@ -25,7 +25,7 @@ filegroup { java_library { name: "service-remoteauth-pre-jarjar", srcs: [":remoteauth-service-srcs"], - + required: ["libremoteauth_jni_rust_defaults"], defaults: [ "framework-system-server-module-defaults" ], diff --git a/remoteauth/service/jni/Android.bp b/remoteauth/service/jni/Android.bp new file mode 100644 index 0000000000..e6e8a437e1 --- /dev/null +++ b/remoteauth/service/jni/Android.bp @@ -0,0 +1,76 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +rust_defaults { + name: "libremoteauth_jni_rust_defaults", + crate_name: "remoteauth_jni_rust", + lints: "android", + clippy_lints: "android", + min_sdk_version: "35", + srcs: ["src/lib.rs"], + rustlibs: [ + "libbinder_rs", + "libjni", + "liblazy_static", + "liblog_rust", + "liblogger", + "libnum_traits", + "libthiserror", + "libtokio", + "libanyhow", + ], + proc_macros: [ + "libasync_trait", + ], + prefer_rlib: true, + apex_available: [ + "com.android.remoteauth", + ], + host_supported: true, +} + +rust_test { + name: "libremoteauth_jni_rust_tests", + defaults: ["libremoteauth_jni_rust_defaults"], + rustlibs: [ + ], + target: { + android: { + test_suites: [ + "general-tests", + ], + test_config_template: "remoteauth_rust_test_config_template.xml", + }, + host: { + test_suites: [ + "general-tests", + ], + data_libs: [ + "libandroid_runtime_lazy", + "libbase", + "libbinder", + "libbinder_ndk", + "libcutils", + "liblog", + "libutils", + ], + }, + }, + test_options: { + unit_test: true, + }, + // Support multilib variants (using different suffix per sub-architecture), which is needed on + // build targets with secondary architectures, as the MTS test suite packaging logic flattens + // all test artifacts into a single `testcases` directory. + compile_multilib: "both", + multilib: { + lib32: { + suffix: "32", + }, + lib64: { + suffix: "64", + }, + }, + auto_gen_config: true, +} diff --git a/remoteauth/service/jni/remoteauth_rust_test_config_template.xml b/remoteauth/service/jni/remoteauth_rust_test_config_template.xml new file mode 100644 index 0000000000..673b45184d --- /dev/null +++ b/remoteauth/service/jni/remoteauth_rust_test_config_template.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/remoteauth/service/jni/src/jnames.rs b/remoteauth/service/jni/src/jnames.rs new file mode 100644 index 0000000000..d7cc908df4 --- /dev/null +++ b/remoteauth/service/jni/src/jnames.rs @@ -0,0 +1,17 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Name of java classes and methods for RemoteAuth platform: +pub(crate) const SEND_REQUEST_MNAME: &str = "sendRequest"; +pub(crate) const SEND_REQUEST_MSIG: &str = "(I[BII)V"; diff --git a/remoteauth/service/jni/src/lib.rs b/remoteauth/service/jni/src/lib.rs new file mode 100644 index 0000000000..0c18679c38 --- /dev/null +++ b/remoteauth/service/jni/src/lib.rs @@ -0,0 +1,24 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! New rust RemoteAuth JNI library. +//! +//! This library takes the JNI calls from RemoteAuthService to the remoteauth protocol library +//! and from protocol library to platform (Java interface) + +mod jnames; +mod unique_jvm; + +//pub mod remoteauth_jni_android_protocol; +pub mod remoteauth_jni_android_platform; diff --git a/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs b/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs new file mode 100644 index 0000000000..4597561c2f --- /dev/null +++ b/remoteauth/service/jni/src/remoteauth_jni_android_platform.rs @@ -0,0 +1,306 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::jnames::{SEND_REQUEST_MNAME, SEND_REQUEST_MSIG}; +use crate::unique_jvm; +use anyhow::anyhow; +use async_trait::async_trait; +use jni::errors::Error as JNIError; +use jni::objects::{GlobalRef, JMethodID, JObject, JValue}; +use jni::signature::TypeSignature; +use jni::sys::{jbyteArray, jint, jlong, jvalue}; +use jni::{JNIEnv, JavaVM}; +use lazy_static::lazy_static; +use log::{debug, error, info}; +use std::collections::HashMap; +use std::sync::{ + atomic::{AtomicI64, Ordering}, + Arc, +}; +use tokio::{ + runtime::Runtime, + sync::{mpsc, Mutex}, +}; + +/// Macro capturing the name of the function calling this macro. +/// +/// function_name()! -> &'static str +/// Returns the function name as 'static reference. +macro_rules! function_name { + () => {{ + // Declares function f inside current function. + fn f() {} + fn type_name_of(_: T) -> &'static str { + std::any::type_name::() + } + // type name of f is struct_or_crate_name::calling_function_name::f + let name = type_name_of(f); + // Find and cut the rest of the path: + // Third to last character, up to the first semicolon: is calling_function_name + match &name[..name.len() - 3].rfind(':') { + Some(pos) => &name[pos + 1..name.len() - 3], + None => &name[..name.len() - 3], + } + }}; +} + +lazy_static! { + static ref HANDLE_MAPPING: Mutex>>> = + Mutex::new(HashMap::new()); + static ref HANDLE_RN: AtomicI64 = AtomicI64::new(0); +} + +fn generate_platform_handle() -> i64 { + HANDLE_RN.fetch_add(1, Ordering::SeqCst) +} + +async fn insert_platform_handle(handle: i64, item: Arc>) { + if 0 == handle { + // Init once + logger::init( + logger::Config::default() + .with_tag_on_device("remoteauth") + .with_min_level(log::Level::Trace) + .with_filter("trace,jni=info"), + ); + } + HANDLE_MAPPING.lock().await.insert(handle, Arc::clone(&item)); +} + +#[async_trait] +pub trait Platform { + /// Send a binary message to the remote with the given connection id and return the response. + async fn send_request(&mut self, connection_id: i32, request: &[u8]) + -> anyhow::Result>; +} +////////////////////////////////// + +pub struct JavaPlatform { + platform_handle: i64, + vm: &'static Arc, + platform_native_obj: GlobalRef, + send_request_method_id: JMethodID, + map_futures: Mutex>>>, + atomic_handle: AtomicI64, +} + +impl JavaPlatform { + // Method to create JavaPlatform + pub async fn create<'a>( + env: JNIEnv<'a>, + java_platform_native: JObject<'a>, + ) -> Result>, JNIError> { + let jvm = env.get_java_vm()?; + let _ = unique_jvm::set_once(jvm); + let platform_handle = generate_platform_handle(); + let platform = Arc::new(Mutex::new(JavaPlatform::new( + platform_handle, + unique_jvm::get_static_ref().ok_or(JNIError::InvalidCtorReturn)?, + java_platform_native, + )?)); + insert_platform_handle(platform_handle, Arc::clone(&platform)).await; + Ok(Arc::clone(&platform)) + } + + fn new( + platform_handle: i64, + vm: &'static Arc, + java_platform_native: JObject, + ) -> Result { + vm.attach_current_thread().and_then(|env| { + let platform_class = env.get_object_class(java_platform_native)?; + let platform_native_obj = env.new_global_ref(java_platform_native)?; + let send_request_method: JMethodID = + env.get_method_id(platform_class, SEND_REQUEST_MNAME, SEND_REQUEST_MSIG)?; + + Ok(Self { + platform_handle, + vm, + platform_native_obj, + send_request_method_id: send_request_method, + map_futures: Mutex::new(HashMap::new()), + atomic_handle: AtomicI64::new(0), + }) + }) + } +} + +#[async_trait] +impl Platform for JavaPlatform { + async fn send_request( + &mut self, + connection_id: i32, + request: &[u8], + ) -> anyhow::Result> { + let type_signature = TypeSignature::from_str(SEND_REQUEST_MSIG) + .map_err(|e| anyhow!("JNI: Invalid type signature: {:?}", e))?; + + let (tx, mut rx) = mpsc::channel(1); + let response_handle = self.atomic_handle.fetch_add(1, Ordering::SeqCst); + self.map_futures.lock().await.insert(response_handle, tx); + self.vm + .attach_current_thread() + .and_then(|env| { + let request_jbytearray = env.byte_array_from_slice(request)?; + // Safety: request_jbytearray is safely instantiated above. + let request_jobject = unsafe { JObject::from_raw(request_jbytearray) }; + + let _ = env.call_method_unchecked( + self.platform_native_obj.as_obj(), + self.send_request_method_id, + type_signature.ret, + &[ + jvalue::from(JValue::Int(connection_id)), + jvalue::from(JValue::Object(request_jobject)), + jvalue::from(JValue::Long(response_handle)), + jvalue::from(JValue::Long(self.platform_handle)), + ], + ); + Ok(info!( + "{} successfully sent-message, waiting for response {}:{}", + function_name!(), + self.platform_handle, + response_handle + )) + }) + .map_err(|e| anyhow!("JNI: Failed to attach current thread: {:?}", e))?; + + rx.recv().await.ok_or(anyhow!("{} failed in awaiting for a result", function_name!())) + } +} + +impl JavaPlatform { + async fn on_send_request_success(&mut self, response: &[u8], response_handle: i64) { + info!( + "{} completed successfully {}:{}", + function_name!(), + self.platform_handle, + response_handle + ); + if let Some(tx) = self.map_futures.lock().await.remove(&response_handle) { + let _ = tx.send(response.to_vec()).await; + } else { + error!( + "Failed to find TX for {} and {}:{}", + function_name!(), + self.platform_handle, + response_handle + ); + } + } + + async fn on_send_request_error(&self, error_code: i32, response_handle: i64) { + error!( + "{} completed with error {} {}:{}", + function_name!(), + error_code, + self.platform_handle, + response_handle + ); + if let Some(tx) = self.map_futures.lock().await.remove(&response_handle) { + // `rx.recv()` ends with `Err` + drop(tx); + } else { + error!( + "Failed to find TX for {} and {}:{}", + function_name!(), + self.platform_handle, + response_handle + ); + } + } +} + +#[no_mangle] +pub extern "system" fn Java_com_android_server_remoteauth_jni_NativeRemoteAuthJavaPlatform_native_on_send_request_success( + env: JNIEnv, + _: JObject, + app_response: jbyteArray, + platform_handle: jlong, + response_handle: jlong, +) { + debug!("{}: enter", function_name!()); + Runtime::new().unwrap().block_on(native_on_send_request_success( + env, + app_response, + platform_handle, + response_handle, + )); +} + +async fn native_on_send_request_success( + env: JNIEnv<'_>, + app_response: jbyteArray, + platform_handle: jlong, + response_handle: jlong, +) { + if let Some(platform) = HANDLE_MAPPING.lock().await.get(&platform_handle) { + let response = + env.convert_byte_array(app_response).map_err(|_| JNIError::InvalidCtorReturn).unwrap(); + let mut platform = (*platform).lock().await; + platform.on_send_request_success(&response, response_handle).await; + } else { + let _ = env.throw_new( + "com/android/server/remoteauth/jni/BadHandleException", + format!("Failed to find Platform with ID {} in {}", platform_handle, function_name!()), + ); + } +} + +#[no_mangle] +pub extern "system" fn Java_com_android_server_remoteauth_jni_NativeRemoteAuthJavaPlatform_native_on_send_request_error( + env: JNIEnv, + _: JObject, + error_code: jint, + platform_handle: jlong, + response_handle: jlong, +) { + debug!("{}: enter", function_name!()); + Runtime::new().unwrap().block_on(native_on_send_request_error( + env, + error_code, + platform_handle, + response_handle, + )); +} + +async fn native_on_send_request_error( + env: JNIEnv<'_>, + error_code: jint, + platform_handle: jlong, + response_handle: jlong, +) { + if let Some(platform) = HANDLE_MAPPING.lock().await.get(&platform_handle) { + let platform = (*platform).lock().await; + platform.on_send_request_error(error_code, response_handle).await; + } else { + let _ = env.throw_new( + "com/android/server/remoteauth/jni/BadHandleException", + format!("Failed to find Platform with ID {} in {}", platform_handle, function_name!()), + ); + } +} + +#[cfg(test)] +mod tests { + //use super::*; + + //use tokio::runtime::Builder; + + /// Checks validity of the function_name! macro. + #[test] + fn test_function_name() { + assert_eq!(function_name!(), "test_function_name"); + } +} diff --git a/remoteauth/service/jni/src/unique_jvm.rs b/remoteauth/service/jni/src/unique_jvm.rs new file mode 100644 index 0000000000..46cc361dbf --- /dev/null +++ b/remoteauth/service/jni/src/unique_jvm.rs @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! takes a JavaVM to a static reference. +//! +//! JavaVM is shared as multiple JavaVM within a single process is not allowed +//! per [JNI spec](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/invocation.html) +//! The unique JavaVM need to be shared over (potentially) different threads. + +use std::sync::{Arc, Once}; + +use anyhow::Result; +use jni::JavaVM; + +static mut JVM: Option> = None; +static INIT: Once = Once::new(); +/// set_once sets the unique JavaVM that can be then accessed using get_static_ref() +/// +/// The function shall only be called once. +pub(crate) fn set_once(jvm: JavaVM) -> Result<()> { + // Safety: follows [this pattern](https://doc.rust-lang.org/std/sync/struct.Once.html). + // Modification to static mut is nested inside call_once. + unsafe { + INIT.call_once(|| { + JVM = Some(Arc::new(jvm)); + }); + } + Ok(()) +} +/// Gets a 'static reference to the unique JavaVM. Returns None if set_once() was never called. +pub(crate) fn get_static_ref() -> Option<&'static Arc> { + // Safety: follows [this pattern](https://doc.rust-lang.org/std/sync/struct.Once.html). + // Modification to static mut is nested inside call_once. + unsafe { JVM.as_ref() } +}