diff options
| author | 2021-09-15 12:56:10 +0000 | |
|---|---|---|
| committer | 2021-11-02 09:47:14 +0000 | |
| commit | 05f5a2c4aa35cc0b90b1f90ef1f7f1a7cbca687d (patch) | |
| tree | f49ddb79194794b4c4997fb8d8adecdb0a99710e | |
| parent | 676e9a9ede4f2e9f583b21c88ea6c977c034efc2 (diff) | |
binder: add async Rust support
Test: add and run integration tests
Change-Id: I7671eeb7dfe4cc45efd57756753b361440529a3c
| -rw-r--r-- | libs/binder/rust/Android.bp | 21 | ||||
| -rw-r--r-- | libs/binder/rust/binder_tokio/lib.rs | 65 | ||||
| -rw-r--r-- | libs/binder/rust/src/binder.rs | 79 | ||||
| -rw-r--r-- | libs/binder/rust/src/binder_async.rs | 55 | ||||
| -rw-r--r-- | libs/binder/rust/src/lib.rs | 7 | ||||
| -rw-r--r-- | libs/binder/rust/src/parcel.rs | 13 | ||||
| -rw-r--r-- | libs/binder/rust/tests/Android.bp | 2 | ||||
| -rw-r--r-- | libs/binder/rust/tests/integration.rs | 125 |
8 files changed, 353 insertions, 14 deletions
diff --git a/libs/binder/rust/Android.bp b/libs/binder/rust/Android.bp index ecb044e589..d323022b01 100644 --- a/libs/binder/rust/Android.bp +++ b/libs/binder/rust/Android.bp @@ -33,6 +33,27 @@ rust_library { } rust_library { + name: "libbinder_tokio_rs", + crate_name: "binder_tokio", + srcs: ["binder_tokio/lib.rs"], + rustlibs: [ + "libbinder_rs", + "libtokio", + ], + host_supported: true, + target: { + darwin: { + enabled: false, + } + }, + apex_available: [ + "//apex_available:platform", + "com.android.compos", + "com.android.virt", + ], +} + +rust_library { name: "libbinder_ndk_sys", crate_name: "binder_ndk_sys", srcs: [ diff --git a/libs/binder/rust/binder_tokio/lib.rs b/libs/binder/rust/binder_tokio/lib.rs new file mode 100644 index 0000000000..a71ddda073 --- /dev/null +++ b/libs/binder/rust/binder_tokio/lib.rs @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021 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. + */ + +//! This crate lets you use the Tokio `spawn_blocking` pool with AIDL in async +//! Rust code. +//! +//! This crate works by defining a type [`Tokio`], which you can use as the +//! generic parameter in the async version of the trait generated by the AIDL +//! compiler. +//! ```text +//! use binder_tokio::Tokio; +//! +//! binder::get_interface::<dyn SomeAsyncInterface<Tokio>>("..."). +//! ``` +//! +//! [`Tokio`]: crate::Tokio + +use binder::public_api::{BinderAsyncPool, BoxFuture}; +use binder::StatusCode; +use std::future::Future; + +/// Use the Tokio `spawn_blocking` pool with AIDL. +pub enum Tokio {} + +impl BinderAsyncPool for Tokio { + fn spawn<'a, F1, F2, Fut, A, B, E>(spawn_me: F1, after_spawn: F2) -> BoxFuture<'a, Result<B, E>> + where + F1: FnOnce() -> A, + F2: FnOnce(A) -> Fut, + Fut: Future<Output = Result<B, E>>, + F1: Send + 'static, + F2: Send + 'a, + Fut: Send + 'a, + A: Send + 'static, + B: Send + 'a, + E: From<crate::StatusCode>, + { + let handle = tokio::task::spawn_blocking(spawn_me); + Box::pin(async move { + // The `is_panic` branch is not actually reachable in Android as we compile + // with `panic = abort`. + match handle.await { + Ok(res) => after_spawn(res).await, + Err(e) if e.is_panic() => std::panic::resume_unwind(e.into_panic()), + Err(e) if e.is_cancelled() => Err(StatusCode::FAILED_TRANSACTION.into()), + Err(_) => Err(StatusCode::UNKNOWN_ERROR.into()), + } + }) + } +} + + diff --git a/libs/binder/rust/src/binder.rs b/libs/binder/rust/src/binder.rs index cc5dd06c9b..4e048d7c5a 100644 --- a/libs/binder/rust/src/binder.rs +++ b/libs/binder/rust/src/binder.rs @@ -713,12 +713,14 @@ macro_rules! declare_binder_interface { $interface:path[$descriptor:expr] { native: $native:ident($on_transact:path), proxy: $proxy:ident, + $(async: $async_interface:ident,)? } } => { $crate::declare_binder_interface! { $interface[$descriptor] { native: $native($on_transact), proxy: $proxy {}, + $(async: $async_interface,)? stability: $crate::Stability::default(), } } @@ -728,6 +730,7 @@ macro_rules! declare_binder_interface { $interface:path[$descriptor:expr] { native: $native:ident($on_transact:path), proxy: $proxy:ident, + $(async: $async_interface:ident,)? stability: $stability:expr, } } => { @@ -735,6 +738,7 @@ macro_rules! declare_binder_interface { $interface[$descriptor] { native: $native($on_transact), proxy: $proxy {}, + $(async: $async_interface,)? stability: $stability, } } @@ -746,6 +750,7 @@ macro_rules! declare_binder_interface { proxy: $proxy:ident { $($fname:ident: $fty:ty = $finit:expr),* }, + $(async: $async_interface:ident,)? } } => { $crate::declare_binder_interface! { @@ -754,6 +759,7 @@ macro_rules! declare_binder_interface { proxy: $proxy { $($fname: $fty = $finit),* }, + $(async: $async_interface,)? stability: $crate::Stability::default(), } } @@ -765,6 +771,7 @@ macro_rules! declare_binder_interface { proxy: $proxy:ident { $($fname:ident: $fty:ty = $finit:expr),* }, + $(async: $async_interface:ident,)? stability: $stability:expr, } } => { @@ -776,6 +783,7 @@ macro_rules! declare_binder_interface { proxy: $proxy { $($fname: $fty = $finit),* }, + $(async: $async_interface,)? stability: $stability, } } @@ -791,6 +799,8 @@ macro_rules! declare_binder_interface { $($fname:ident: $fty:ty = $finit:expr),* }, + $( async: $async_interface:ident, )? + stability: $stability:expr, } } => { @@ -924,7 +934,7 @@ macro_rules! declare_binder_interface { } } - impl std::fmt::Debug for dyn $interface { + impl std::fmt::Debug for dyn $interface + '_ { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.pad(stringify!($interface)) } @@ -938,6 +948,73 @@ macro_rules! declare_binder_interface { .expect(concat!("Error cloning interface ", stringify!($interface))) } } + + $( + // Async interface trait implementations. + impl<P: $crate::BinderAsyncPool> $crate::FromIBinder for dyn $async_interface<P> { + fn try_from(mut ibinder: $crate::SpIBinder) -> $crate::Result<$crate::Strong<dyn $async_interface<P>>> { + use $crate::AssociateClass; + + let existing_class = ibinder.get_class(); + if let Some(class) = existing_class { + if class != <$native as $crate::Remotable>::get_class() && + class.get_descriptor() == <$native as $crate::Remotable>::get_descriptor() + { + // The binder object's descriptor string matches what we + // expect. We still need to treat this local or already + // associated object as remote, because we can't cast it + // into a Rust service object without a matching class + // pointer. + return Ok($crate::Strong::new(Box::new(<$proxy as $crate::Proxy>::from_binder(ibinder)?))); + } + } + + if ibinder.associate_class(<$native as $crate::Remotable>::get_class()) { + let service: $crate::Result<$crate::Binder<$native>> = + std::convert::TryFrom::try_from(ibinder.clone()); + if let Ok(service) = service { + // We were able to associate with our expected class and + // the service is local. + todo!() + //return Ok($crate::Strong::new(Box::new(service))); + } else { + // Service is remote + return Ok($crate::Strong::new(Box::new(<$proxy as $crate::Proxy>::from_binder(ibinder)?))); + } + } + + Err($crate::StatusCode::BAD_TYPE.into()) + } + } + + impl<P: $crate::BinderAsyncPool> $crate::parcel::Serialize for dyn $async_interface<P> + '_ { + fn serialize(&self, parcel: &mut $crate::parcel::Parcel) -> $crate::Result<()> { + let binder = $crate::Interface::as_binder(self); + parcel.write(&binder) + } + } + + impl<P: $crate::BinderAsyncPool> $crate::parcel::SerializeOption for dyn $async_interface<P> + '_ { + fn serialize_option(this: Option<&Self>, parcel: &mut $crate::parcel::Parcel) -> $crate::Result<()> { + parcel.write(&this.map($crate::Interface::as_binder)) + } + } + + impl<P: $crate::BinderAsyncPool> std::fmt::Debug for dyn $async_interface<P> + '_ { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.pad(stringify!($async_interface)) + } + } + + /// Convert a &dyn $async_interface to Strong<dyn $async_interface> + impl<P: $crate::BinderAsyncPool> std::borrow::ToOwned for dyn $async_interface<P> { + type Owned = $crate::Strong<dyn $async_interface<P>>; + fn to_owned(&self) -> Self::Owned { + self.as_binder().into_interface() + .expect(concat!("Error cloning interface ", stringify!($async_interface))) + } + } + )? }; } diff --git a/libs/binder/rust/src/binder_async.rs b/libs/binder/rust/src/binder_async.rs new file mode 100644 index 0000000000..214c0b5f26 --- /dev/null +++ b/libs/binder/rust/src/binder_async.rs @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 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. + */ + +use std::future::Future; +use std::pin::Pin; + +/// A type alias for a pinned, boxed future that lets you write shorter code without littering it +/// with Pin and Send bounds. +pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>; + +/// A thread pool for running binder transactions. +pub trait BinderAsyncPool { + /// This function should conceptually behave like this: + /// + /// ```text + /// let result = spawn_thread(|| spawn_me()).await; + /// return after_spawn(result).await; + /// ``` + /// + /// If the spawning fails for some reason, the method may also skip the `after_spawn` closure + /// and immediately return an error. + /// + /// The only difference between different implementations should be which + /// `spawn_thread` method is used. For Tokio, it would be `tokio::task::spawn_blocking`. + /// + /// This method has the design it has because the only way to define a trait that + /// allows the return type of the spawn to be chosen by the caller is to return a + /// boxed `Future` trait object, and including `after_spawn` in the trait function + /// allows the caller to avoid double-boxing if they want to do anything to the value + /// returned from the spawned thread. + fn spawn<'a, F1, F2, Fut, A, B, E>(spawn_me: F1, after_spawn: F2) -> BoxFuture<'a, Result<B, E>> + where + F1: FnOnce() -> A, + F2: FnOnce(A) -> Fut, + Fut: Future<Output = Result<B, E>>, + F1: Send + 'static, + F2: Send + 'a, + Fut: Send + 'a, + A: Send + 'static, + B: Send + 'a, + E: From<crate::StatusCode>; +} diff --git a/libs/binder/rust/src/lib.rs b/libs/binder/rust/src/lib.rs index 81b620e85f..2ac2d2f0bc 100644 --- a/libs/binder/rust/src/lib.rs +++ b/libs/binder/rust/src/lib.rs @@ -98,6 +98,7 @@ mod proxy; #[macro_use] mod binder; +mod binder_async; mod error; mod native; mod state; @@ -111,6 +112,7 @@ pub use crate::binder::{ Stability, Strong, TransactionCode, TransactionFlags, Weak, FIRST_CALL_TRANSACTION, FLAG_CLEAR_BUF, FLAG_ONEWAY, FLAG_PRIVATE_LOCAL, LAST_CALL_TRANSACTION, }; +pub use crate::binder_async::{BoxFuture, BinderAsyncPool}; pub use error::{status_t, ExceptionCode, Result, Status, StatusCode}; pub use native::{add_service, force_lazy_services_persist, register_lazy_service, Binder}; pub use parcel::{OwnedParcel, Parcel}; @@ -133,8 +135,9 @@ pub mod public_api { wait_for_interface, }; pub use super::{ - BinderFeatures, DeathRecipient, ExceptionCode, IBinder, Interface, ProcessState, SpIBinder, - Status, StatusCode, Strong, ThreadState, Weak, WpIBinder, + BinderAsyncPool, BinderFeatures, BoxFuture, DeathRecipient, ExceptionCode, IBinder, + Interface, ProcessState, SpIBinder, Status, StatusCode, Strong, ThreadState, Weak, + WpIBinder, }; /// Binder result containing a [`Status`] on error. diff --git a/libs/binder/rust/src/parcel.rs b/libs/binder/rust/src/parcel.rs index 9dba950677..a0e147860d 100644 --- a/libs/binder/rust/src/parcel.rs +++ b/libs/binder/rust/src/parcel.rs @@ -83,6 +83,19 @@ impl OwnedParcel { Self { ptr } } + /// Convert the provided parcel to an owned parcel, or return `None` if it + /// is borrowed. + pub fn try_from(parcel: Parcel) -> Option<OwnedParcel> { + match &parcel { + Parcel::Owned(ptr) => { + let ptr = *ptr; + std::mem::forget(parcel); + Some(OwnedParcel { ptr }) + } + Parcel::Borrowed(_) => None, + } + } + /// Create an owned reference to a parcel object from a raw pointer. /// /// # Safety diff --git a/libs/binder/rust/tests/Android.bp b/libs/binder/rust/tests/Android.bp index ecc61f4683..2d1175be75 100644 --- a/libs/binder/rust/tests/Android.bp +++ b/libs/binder/rust/tests/Android.bp @@ -13,6 +13,8 @@ rust_test { rustlibs: [ "libbinder_rs", "libselinux_bindgen", + "libbinder_tokio_rs", + "libtokio", ], shared_libs: [ "libselinux", diff --git a/libs/binder/rust/tests/integration.rs b/libs/binder/rust/tests/integration.rs index 335e8d848f..3c2907393f 100644 --- a/libs/binder/rust/tests/integration.rs +++ b/libs/binder/rust/tests/integration.rs @@ -17,7 +17,7 @@ //! Rust Binder crate integration tests use binder::declare_binder_interface; -use binder::parcel::Parcel; +use binder::parcel::{Parcel, OwnedParcel}; use binder::{ Binder, BinderFeatures, IBinderInternal, Interface, StatusCode, ThreadState, TransactionCode, FIRST_CALL_TRANSACTION, @@ -154,12 +154,25 @@ pub trait ITest: Interface { fn get_selinux_context(&self) -> binder::Result<String>; } +/// Async trivial testing binder interface +pub trait IATest<P>: Interface { + /// Returns a test string + fn test(&self) -> binder::BoxFuture<'static, binder::Result<String>>; + + /// Return the arguments sent via dump + fn get_dump_args(&self) -> binder::BoxFuture<'static, binder::Result<Vec<String>>>; + + /// Returns the caller's SELinux context + fn get_selinux_context(&self) -> binder::BoxFuture<'static, binder::Result<String>>; +} + declare_binder_interface! { ITest["android.os.ITest"] { native: BnTest(on_transact), proxy: BpTest { x: i32 = 100 }, + async: IATest, } } @@ -201,6 +214,32 @@ impl ITest for BpTest { } } +impl<P: binder::BinderAsyncPool> IATest<P> for BpTest { + fn test(&self) -> binder::BoxFuture<'static, binder::Result<String>> { + let binder = self.binder.clone(); + P::spawn( + move || binder.transact(TestTransactionCode::Test as TransactionCode, 0, |_| Ok(())).map(|p| OwnedParcel::try_from(p).unwrap()), + |reply| async move { reply?.into_parcel().read() } + ) + } + + fn get_dump_args(&self) -> binder::BoxFuture<'static, binder::Result<Vec<String>>> { + let binder = self.binder.clone(); + P::spawn( + move || binder.transact(TestTransactionCode::GetDumpArgs as TransactionCode, 0, |_| Ok(())).map(|p| OwnedParcel::try_from(p).unwrap()), + |reply| async move { reply?.into_parcel().read() } + ) + } + + fn get_selinux_context(&self) -> binder::BoxFuture<'static, binder::Result<String>> { + let binder = self.binder.clone(); + P::spawn( + move || binder.transact(TestTransactionCode::GetSelinuxContext as TransactionCode, 0, |_| Ok(())).map(|p| OwnedParcel::try_from(p).unwrap()), + |reply| async move { reply?.into_parcel().read() } + ) + } +} + impl ITest for Binder<BnTest> { fn test(&self) -> binder::Result<String> { self.0.test() @@ -215,6 +254,23 @@ impl ITest for Binder<BnTest> { } } +impl<P: binder::BinderAsyncPool> IATest<P> for Binder<BnTest> { + fn test(&self) -> binder::BoxFuture<'static, binder::Result<String>> { + let res = self.0.test(); + Box::pin(async move { res }) + } + + fn get_dump_args(&self) -> binder::BoxFuture<'static, binder::Result<Vec<String>>> { + let res = self.0.get_dump_args(); + Box::pin(async move { res }) + } + + fn get_selinux_context(&self) -> binder::BoxFuture<'static, binder::Result<String>> { + let res = self.0.get_selinux_context(); + Box::pin(async move { res }) + } +} + /// Trivial testing binder interface pub trait ITestSameDescriptor: Interface {} @@ -255,7 +311,9 @@ mod tests { SpIBinder, StatusCode, Strong, }; - use super::{BnTest, ITest, ITestSameDescriptor, TestService, RUST_SERVICE_BINARY}; + use binder_tokio::Tokio; + + use super::{BnTest, ITest, IATest, ITestSameDescriptor, TestService, RUST_SERVICE_BINARY}; pub struct ScopedServiceProcess(Child); @@ -303,12 +361,20 @@ mod tests { binder::get_interface::<dyn ITest>("this_service_does_not_exist").err(), Some(StatusCode::NAME_NOT_FOUND) ); + assert_eq!( + binder::get_interface::<dyn IATest<Tokio>>("this_service_does_not_exist").err(), + Some(StatusCode::NAME_NOT_FOUND) + ); // The service manager service isn't an ITest, so this must fail. assert_eq!( binder::get_interface::<dyn ITest>("manager").err(), Some(StatusCode::BAD_TYPE) ); + assert_eq!( + binder::get_interface::<dyn IATest<Tokio>>("manager").err(), + Some(StatusCode::BAD_TYPE) + ); } #[test] @@ -323,6 +389,10 @@ mod tests { binder::wait_for_interface::<dyn ITest>("manager").err(), Some(StatusCode::BAD_TYPE) ); + assert_eq!( + binder::wait_for_interface::<dyn IATest<Tokio>>("manager").err(), + Some(StatusCode::BAD_TYPE) + ); } #[test] @@ -334,6 +404,15 @@ mod tests { assert_eq!(test_client.test().unwrap(), "trivial_client_test"); } + #[tokio::test] + async fn trivial_client_async() { + let service_name = "trivial_client_test"; + let _process = ScopedServiceProcess::new(service_name); + let test_client: Strong<dyn IATest<Tokio>> = + binder::get_interface(service_name).expect("Did not get manager binder service"); + assert_eq!(test_client.test().await.unwrap(), "trivial_client_test"); + } + #[test] fn wait_for_trivial_client() { let service_name = "wait_for_trivial_client_test"; @@ -343,23 +422,47 @@ mod tests { assert_eq!(test_client.test().unwrap(), "wait_for_trivial_client_test"); } + #[tokio::test] + async fn wait_for_trivial_client_async() { + let service_name = "wait_for_trivial_client_test"; + let _process = ScopedServiceProcess::new(service_name); + let test_client: Strong<dyn IATest<Tokio>> = + binder::wait_for_interface(service_name).expect("Did not get manager binder service"); + assert_eq!(test_client.test().await.unwrap(), "wait_for_trivial_client_test"); + } + + fn get_expected_selinux_context() -> &'static str { + unsafe { + let mut out_ptr = ptr::null_mut(); + assert_eq!(selinux_sys::getcon(&mut out_ptr), 0); + assert!(!out_ptr.is_null()); + CStr::from_ptr(out_ptr) + .to_str() + .expect("context was invalid UTF-8") + } + } + #[test] fn get_selinux_context() { let service_name = "get_selinux_context"; let _process = ScopedServiceProcess::new(service_name); let test_client: Strong<dyn ITest> = binder::get_interface(service_name).expect("Did not get manager binder service"); - let expected_context = unsafe { - let mut out_ptr = ptr::null_mut(); - assert_eq!(selinux_sys::getcon(&mut out_ptr), 0); - assert!(!out_ptr.is_null()); - CStr::from_ptr(out_ptr) - }; assert_eq!( test_client.get_selinux_context().unwrap(), - expected_context - .to_str() - .expect("context was invalid UTF-8"), + get_expected_selinux_context() + ); + } + + #[tokio::test] + async fn get_selinux_context_async() { + let service_name = "get_selinux_context"; + let _process = ScopedServiceProcess::new(service_name); + let test_client: Strong<dyn IATest<Tokio>> = + binder::get_interface(service_name).expect("Did not get manager binder service"); + assert_eq!( + test_client.get_selinux_context().await.unwrap(), + get_expected_selinux_context() ); } |