Merge "Implement Discovery PLATFORM logic in Rust" into main
This commit is contained in:
@@ -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",
|
||||
],
|
||||
|
||||
76
remoteauth/service/jni/Android.bp
Normal file
76
remoteauth/service/jni/Android.bp
Normal file
@@ -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,
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
<configuration description="Configuration for {MODULE} Rust tests">
|
||||
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
|
||||
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
|
||||
<option name="cleanup" value="true" />
|
||||
<option name="push" value="{MODULE}->/data/local/tmp/{MODULE}" />
|
||||
<option name="append-bitness" value="true" />
|
||||
</target_preparer>
|
||||
<test class="com.android.tradefed.testtype.rust.RustBinaryTest" >
|
||||
<option name="test-device-path" value="/data/local/tmp" />
|
||||
<option name="module-name" value="{MODULE}" />
|
||||
</test>
|
||||
<object type="module_controller"
|
||||
class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
|
||||
<option name="mainline-module-package-name" value="com.google.android.remoteauth" />
|
||||
</object>
|
||||
</configuration>
|
||||
17
remoteauth/service/jni/src/jnames.rs
Normal file
17
remoteauth/service/jni/src/jnames.rs
Normal file
@@ -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";
|
||||
24
remoteauth/service/jni/src/lib.rs
Normal file
24
remoteauth/service/jni/src/lib.rs
Normal file
@@ -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;
|
||||
306
remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
Normal file
306
remoteauth/service/jni/src/remoteauth_jni_android_platform.rs
Normal file
@@ -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>(_: T) -> &'static str {
|
||||
std::any::type_name::<T>()
|
||||
}
|
||||
// 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<HashMap<i64, Arc<Mutex<JavaPlatform>>>> =
|
||||
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<Mutex<JavaPlatform>>) {
|
||||
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<Vec<u8>>;
|
||||
}
|
||||
//////////////////////////////////
|
||||
|
||||
pub struct JavaPlatform {
|
||||
platform_handle: i64,
|
||||
vm: &'static Arc<JavaVM>,
|
||||
platform_native_obj: GlobalRef,
|
||||
send_request_method_id: JMethodID,
|
||||
map_futures: Mutex<HashMap<i64, mpsc::Sender<Vec<u8>>>>,
|
||||
atomic_handle: AtomicI64,
|
||||
}
|
||||
|
||||
impl JavaPlatform {
|
||||
// Method to create JavaPlatform
|
||||
pub async fn create<'a>(
|
||||
env: JNIEnv<'a>,
|
||||
java_platform_native: JObject<'a>,
|
||||
) -> Result<Arc<Mutex<impl Platform>>, 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<JavaVM>,
|
||||
java_platform_native: JObject,
|
||||
) -> Result<JavaPlatform, JNIError> {
|
||||
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<Vec<u8>> {
|
||||
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");
|
||||
}
|
||||
}
|
||||
48
remoteauth/service/jni/src/unique_jvm.rs
Normal file
48
remoteauth/service/jni/src/unique_jvm.rs
Normal file
@@ -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<Arc<JavaVM>> = 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<JavaVM>> {
|
||||
// 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() }
|
||||
}
|
||||
Reference in New Issue
Block a user