Merge "Change argument of active services callback to bool."
diff --git a/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl b/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl
index 85e6969..50c1624 100644
--- a/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl
+++ b/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl
@@ -21,8 +21,6 @@
*
* <p>When bugreport creation is complete one of {@code onError} or {@code onFinished} is called.
*
- * <p>These methods are synchronous by design in order to make dumpstate's lifecycle simpler
- * to handle.
*
* {@hide}
*/
@@ -54,10 +52,8 @@
/**
* Called on an error condition with one of the error codes listed above.
- * This is not an asynchronous method since it can race with dumpstate exiting, thus triggering
- * death recipient.
*/
- void onError(int errorCode);
+ oneway void onError(int errorCode);
/**
* Called when taking bugreport finishes successfully.
diff --git a/libs/binder/TEST_MAPPING b/libs/binder/TEST_MAPPING
index 00a14f4..2e90142 100644
--- a/libs/binder/TEST_MAPPING
+++ b/libs/binder/TEST_MAPPING
@@ -65,6 +65,9 @@
},
{
"name": "binderRustNdkInteropTest"
+ },
+ {
+ "name": "rustBinderSerializationTest"
}
]
}
diff --git a/libs/binder/rust/src/binder.rs b/libs/binder/rust/src/binder.rs
index ed3b9ec..42c1e0a 100644
--- a/libs/binder/rust/src/binder.rs
+++ b/libs/binder/rust/src/binder.rs
@@ -613,7 +613,7 @@
impl $crate::parcel::Serialize for dyn $interface + '_
where
- $interface: $crate::Interface
+ dyn $interface: $crate::Interface
{
fn serialize(&self, parcel: &mut $crate::parcel::Parcel) -> $crate::Result<()> {
let binder = $crate::Interface::as_binder(self);
diff --git a/libs/binder/rust/src/parcel/parcelable.rs b/libs/binder/rust/src/parcel/parcelable.rs
index 138b360..8d18fb4 100644
--- a/libs/binder/rust/src/parcel/parcelable.rs
+++ b/libs/binder/rust/src/parcel/parcelable.rs
@@ -15,15 +15,17 @@
*/
use crate::binder::{AsNative, FromIBinder};
-use crate::error::{status_result, Result, Status, StatusCode};
+use crate::error::{status_result, status_t, Result, Status, StatusCode};
use crate::parcel::Parcel;
use crate::proxy::SpIBinder;
use crate::sys;
use std::convert::TryInto;
use std::ffi::c_void;
-use std::os::raw::c_char;
+use std::os::raw::{c_char, c_ulong};
+use std::mem::{self, MaybeUninit};
use std::ptr;
+use std::slice;
/// A struct whose instances can be written to a [`Parcel`].
// Might be able to hook this up as a serde backend in the future?
@@ -49,38 +51,109 @@
pub trait SerializeArray: Serialize + Sized {
/// Serialize an array of this type into the given [`Parcel`].
fn serialize_array(slice: &[Self], parcel: &mut Parcel) -> Result<()> {
- parcel.write_slice_size(Some(slice))?;
-
- for item in slice {
- parcel.write(item)?;
- }
-
- Ok(())
+ let res = unsafe {
+ // Safety: Safe FFI, slice will always be a safe pointer to pass.
+ sys::AParcel_writeParcelableArray(
+ parcel.as_native_mut(),
+ slice.as_ptr() as *const c_void,
+ slice.len().try_into().or(Err(StatusCode::BAD_VALUE))?,
+ Some(serialize_element::<Self>),
+ )
+ };
+ status_result(res)
}
}
+/// Callback to serialize an element of a generic parcelable array.
+///
+/// Safety: We are relying on binder_ndk to not overrun our slice. As long as it
+/// doesn't provide an index larger than the length of the original slice in
+/// serialize_array, this operation is safe. The index provided is zero-based.
+unsafe extern "C" fn serialize_element<T: Serialize>(
+ parcel: *mut sys::AParcel,
+ array: *const c_void,
+ index: c_ulong,
+) -> status_t {
+ // c_ulong and usize are the same, but we need the explicitly sized version
+ // so the function signature matches what bindgen generates.
+ let index = index as usize;
+
+ let slice: &[T] = slice::from_raw_parts(array.cast(), index+1);
+
+ let mut parcel = match Parcel::borrowed(parcel) {
+ None => return StatusCode::UNEXPECTED_NULL as status_t,
+ Some(p) => p,
+ };
+
+ slice[index].serialize(&mut parcel)
+ .err()
+ .unwrap_or(StatusCode::OK)
+ as status_t
+}
+
/// Helper trait for types that can be deserialized as arrays.
/// Defaults to calling Deserialize::deserialize() manually for every element,
/// but can be overridden for custom implementations like `readByteArray`.
pub trait DeserializeArray: Deserialize {
/// Deserialize an array of type from the given [`Parcel`].
fn deserialize_array(parcel: &Parcel) -> Result<Option<Vec<Self>>> {
- let len: i32 = parcel.read()?;
- if len < 0 {
- return Ok(None);
- }
-
- // TODO: Assumes that usize is at least 32 bits
- let mut vec = Vec::with_capacity(len as usize);
-
- for _ in 0..len {
- vec.push(parcel.read()?);
- }
-
- Ok(Some(vec))
+ let mut vec: Option<Vec<MaybeUninit<Self>>> = None;
+ let res = unsafe {
+ // Safety: Safe FFI, vec is the correct opaque type expected by
+ // allocate_vec and deserialize_element.
+ sys::AParcel_readParcelableArray(
+ parcel.as_native(),
+ &mut vec as *mut _ as *mut c_void,
+ Some(allocate_vec::<Self>),
+ Some(deserialize_element::<Self>),
+ )
+ };
+ status_result(res)?;
+ let vec: Option<Vec<Self>> = unsafe {
+ // Safety: We are assuming that the NDK correctly initialized every
+ // element of the vector by now, so we know that all the
+ // MaybeUninits are now properly initialized. We can transmute from
+ // Vec<MaybeUninit<T>> to Vec<T> because MaybeUninit<T> has the same
+ // alignment and size as T, so the pointer to the vector allocation
+ // will be compatible.
+ mem::transmute(vec)
+ };
+ Ok(vec)
}
}
+/// Callback to deserialize a parcelable element.
+///
+/// The opaque array data pointer must be a mutable pointer to an
+/// `Option<Vec<MaybeUninit<T>>>` with at least enough elements for `index` to be valid
+/// (zero-based).
+unsafe extern "C" fn deserialize_element<T: Deserialize>(
+ parcel: *const sys::AParcel,
+ array: *mut c_void,
+ index: c_ulong,
+) -> status_t {
+ // c_ulong and usize are the same, but we need the explicitly sized version
+ // so the function signature matches what bindgen generates.
+ let index = index as usize;
+
+ let vec = &mut *(array as *mut Option<Vec<MaybeUninit<T>>>);
+ let vec = match vec {
+ Some(v) => v,
+ None => return StatusCode::BAD_INDEX as status_t,
+ };
+
+ let parcel = match Parcel::borrowed(parcel as *mut _) {
+ None => return StatusCode::UNEXPECTED_NULL as status_t,
+ Some(p) => p,
+ };
+ let element = match parcel.read() {
+ Ok(e) => e,
+ Err(code) => return code as status_t,
+ };
+ ptr::write(vec[index].as_mut_ptr(), element);
+ StatusCode::OK as status_t
+}
+
/// Helper trait for types that can be nullable when serialized.
// We really need this trait instead of implementing `Serialize for Option<T>`
// because of the Rust orphan rule which prevents us from doing
@@ -115,28 +188,54 @@
/// Callback to allocate a vector for parcel array read functions.
///
+/// This variant is for APIs which use an out buffer pointer.
+///
/// # Safety
///
/// The opaque data pointer passed to the array read function must be a mutable
-/// pointer to an `Option<Vec<T>>`. `buffer` will be assigned a mutable pointer
+/// pointer to an `Option<Vec<MaybeUninit<T>>>`. `buffer` will be assigned a mutable pointer
/// to the allocated vector data if this function returns true.
-unsafe extern "C" fn allocate_vec<T: Clone + Default>(
+unsafe extern "C" fn allocate_vec_with_buffer<T>(
data: *mut c_void,
len: i32,
buffer: *mut *mut T,
) -> bool {
- let vec = &mut *(data as *mut Option<Vec<T>>);
+ let res = allocate_vec::<T>(data, len);
+ let vec = &mut *(data as *mut Option<Vec<MaybeUninit<T>>>);
+ if let Some(new_vec) = vec {
+ *buffer = new_vec.as_mut_ptr() as *mut T;
+ }
+ res
+}
+
+/// Callback to allocate a vector for parcel array read functions.
+///
+/// # Safety
+///
+/// The opaque data pointer passed to the array read function must be a mutable
+/// pointer to an `Option<Vec<MaybeUninit<T>>>`.
+unsafe extern "C" fn allocate_vec<T>(
+ data: *mut c_void,
+ len: i32,
+) -> bool {
+ let vec = &mut *(data as *mut Option<Vec<MaybeUninit<T>>>);
if len < 0 {
*vec = None;
return true;
}
- let mut new_vec: Vec<T> = Vec::with_capacity(len as usize);
- new_vec.resize_with(len as usize, Default::default);
- *buffer = new_vec.as_mut_ptr();
- *vec = Some(new_vec);
+ let mut new_vec: Vec<MaybeUninit<T>> = Vec::with_capacity(len as usize);
+
+ // Safety: We are filling the vector with uninitialized data here, but this
+ // is safe because the vector contains MaybeUninit elements which can be
+ // uninitialized. We're putting off the actual unsafe bit, transmuting the
+ // vector to a Vec<T> until the contents are initialized.
+ new_vec.set_len(len as usize);
+
+ ptr::write(vec, Some(new_vec));
true
}
+
macro_rules! parcelable_primitives {
{
$(
@@ -204,19 +303,29 @@
{DeserializeArray, $ty:ty, $read_array_fn:path} => {
impl DeserializeArray for $ty {
fn deserialize_array(parcel: &Parcel) -> Result<Option<Vec<Self>>> {
- let mut vec: Option<Vec<Self>> = None;
+ let mut vec: Option<Vec<MaybeUninit<Self>>> = None;
let status = unsafe {
// Safety: `Parcel` always contains a valid pointer to an
// `AParcel`. `allocate_vec<T>` expects the opaque pointer to
- // be of type `*mut Option<Vec<T>>`, so `&mut vec` is
+ // be of type `*mut Option<Vec<MaybeUninit<T>>>`, so `&mut vec` is
// correct for it.
$read_array_fn(
parcel.as_native(),
&mut vec as *mut _ as *mut c_void,
- Some(allocate_vec),
+ Some(allocate_vec_with_buffer),
)
};
status_result(status)?;
+ let vec: Option<Vec<Self>> = unsafe {
+ // Safety: We are assuming that the NDK correctly
+ // initialized every element of the vector by now, so we
+ // know that all the MaybeUninits are now properly
+ // initialized. We can transmute from Vec<MaybeUninit<T>> to
+ // Vec<T> because MaybeUninit<T> has the same alignment and
+ // size as T, so the pointer to the vector allocation will
+ // be compatible.
+ mem::transmute(vec)
+ };
Ok(vec)
}
}
@@ -414,7 +523,7 @@
sys::AParcel_readString(
parcel.as_native(),
&mut vec as *mut _ as *mut c_void,
- Some(allocate_vec),
+ Some(allocate_vec_with_buffer),
)
};
diff --git a/libs/binder/rust/src/proxy.rs b/libs/binder/rust/src/proxy.rs
index 17af099..e9e74c0 100644
--- a/libs/binder/rust/src/proxy.rs
+++ b/libs/binder/rust/src/proxy.rs
@@ -70,6 +70,20 @@
ptr.as_mut().map(|p| Self(p))
}
+ /// Extract a raw `AIBinder` pointer from this wrapper.
+ ///
+ /// This method should _only_ be used for testing. Do not try to use the NDK
+ /// interface directly for anything else.
+ ///
+ /// # Safety
+ ///
+ /// The resulting pointer is valid only as long as the SpIBinder is alive.
+ /// The SpIBinder object retains ownership of the AIBinder and the caller
+ /// should not attempt to free the returned pointer.
+ pub unsafe fn as_raw(&self) -> *mut sys::AIBinder {
+ self.0
+ }
+
/// Return true if this binder object is hosted in a different process than
/// the current one.
pub fn is_remote(&self) -> bool {
diff --git a/libs/binder/rust/tests/Android.bp b/libs/binder/rust/tests/Android.bp
index 5ae9c53..8810b5d 100644
--- a/libs/binder/rust/tests/Android.bp
+++ b/libs/binder/rust/tests/Android.bp
@@ -79,3 +79,50 @@
"IBinderRustNdkInteropTest-rust",
],
}
+
+cc_test {
+ name: "rustBinderSerializationTest",
+ shared_libs: [
+ "libbinder",
+ "libbinder_ndk",
+ "libutils",
+ "libbase",
+ ],
+ static_libs: [
+ "libbinder_rs_serialization_test"
+ ],
+ srcs: [
+ "serialization.cpp",
+ ],
+ auto_gen_config: true,
+ test_suites: ["general-tests"],
+}
+
+rust_bindgen {
+ name: "libbinder_rs_serialization_bindgen",
+ crate_name: "binder_rs_serialization_bindgen",
+ wrapper_src: "serialization.hpp",
+ source_stem: "bindings",
+ cpp_std: "gnu++17",
+ bindgen_flags: [
+ "--whitelist-type", "Transaction",
+ "--whitelist-var", "TESTDATA_.*",
+ ],
+
+ shared_libs: [
+ "libbinder",
+ "libc++",
+ ],
+}
+
+rust_ffi_static {
+ name: "libbinder_rs_serialization_test",
+ crate_name: "binder_rs_serialization_test",
+ srcs: [
+ "serialization.rs",
+ ":libbinder_rs_serialization_bindgen",
+ ],
+ rustlibs: [
+ "libbinder_rs",
+ ],
+}
diff --git a/libs/binder/rust/tests/serialization.cpp b/libs/binder/rust/tests/serialization.cpp
new file mode 100644
index 0000000..ec780f2
--- /dev/null
+++ b/libs/binder/rust/tests/serialization.cpp
@@ -0,0 +1,454 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#include <android/binder_ibinder_platform.h>
+#include <android/binder_libbinder.h>
+#include <binder/IServiceManager.h>
+#include <binder/Parcel.h>
+#include <binder/ParcelFileDescriptor.h>
+#include <binder/ProcessState.h>
+#include <binder/Status.h>
+#include <gtest/gtest.h>
+#include <utils/Errors.h>
+#include <utils/String16.h>
+#include "android-base/file.h"
+#include "serialization.hpp"
+
+#include <cmath>
+#include <cstdint>
+#include <iostream>
+#include <optional>
+
+using namespace std;
+using namespace android;
+using android::base::unique_fd;
+using android::os::ParcelFileDescriptor;
+
+// defined in Rust
+extern "C" AIBinder *rust_service();
+
+
+const int8_t TESTDATA_I8[4] = {-128, 0, 117, 127};
+const uint8_t TESTDATA_U8[4] = {0, 42, 117, 255};
+const char16_t TESTDATA_CHARS[4] = {0, 42, 117, numeric_limits<char16_t>::max()};
+const int32_t TESTDATA_I32[4] = {numeric_limits<int32_t>::min(), 0, 117, numeric_limits<int32_t>::max()};
+const int64_t TESTDATA_I64[4] = {numeric_limits<int64_t>::min(), 0, 117, numeric_limits<int64_t>::max()};
+const uint64_t TESTDATA_U64[4] = {0, 42, 117, numeric_limits<uint64_t>::max()};
+const float TESTDATA_FLOAT[4] = {
+ numeric_limits<float>::quiet_NaN(),
+ -numeric_limits<float>::infinity(),
+ 117.0,
+ numeric_limits<float>::infinity(),
+};
+const double TESTDATA_DOUBLE[4] = {
+ numeric_limits<double>::quiet_NaN(),
+ -numeric_limits<double>::infinity(),
+ 117.0,
+ numeric_limits<double>::infinity(),
+};
+const bool TESTDATA_BOOL[4] = {true, false, false, true};
+const char* const TESTDATA_STRS[4] = {"", nullptr, "test", ""};
+
+static ::testing::Environment* gEnvironment;
+
+class SerializationEnvironment : public ::testing::Environment {
+public:
+ void SetUp() override {
+ m_server = AIBinder_toPlatformBinder(rust_service());
+ }
+
+ sp<IBinder> getServer(void) { return m_server; }
+
+private:
+ sp<IBinder> m_server;
+};
+
+
+class SerializationTest : public ::testing::Test {
+protected:
+ void SetUp() override {
+ ASSERT_NE(gEnvironment, nullptr);
+ m_server = static_cast<SerializationEnvironment *>(gEnvironment)->getServer();
+ }
+
+ sp<IBinder> m_server;
+};
+
+
+TEST_F(SerializationTest, SerializeBool) {
+ android::Parcel data;
+ data.writeInterfaceToken(String16("read_parcel_test"));
+
+ vector<bool> bools(begin(TESTDATA_BOOL), end(TESTDATA_BOOL));
+ ASSERT_EQ(data.writeBool(true), OK);
+ ASSERT_EQ(data.writeBool(false), OK);
+ ASSERT_EQ(data.writeBoolVector(bools), OK);
+ ASSERT_EQ(data.writeBoolVector(nullopt), OK);
+
+ android::Parcel reply;
+ ASSERT_EQ(m_server->transact(TEST_BOOL, data, &reply), OK);
+
+ vector<bool> read_bools;
+ optional<vector<bool>> maybe_bools;
+ ASSERT_EQ(reply.readBool(), true);
+ ASSERT_EQ(reply.readBool(), false);
+ ASSERT_EQ(reply.readBoolVector(&read_bools), OK);
+ ASSERT_EQ(read_bools, bools);
+ ASSERT_EQ(reply.readBoolVector(&maybe_bools), OK);
+ ASSERT_EQ(maybe_bools, nullopt);
+
+ int32_t end;
+ ASSERT_EQ(reply.readInt32(&end), NOT_ENOUGH_DATA);
+}
+
+TEST_F(SerializationTest, SerializeByte) {
+ android::Parcel data;
+ data.writeInterfaceToken(String16("read_parcel_test"));
+
+ vector<int8_t> i8s(begin(TESTDATA_I8), end(TESTDATA_I8));
+ vector<uint8_t> u8s(begin(TESTDATA_U8), end(TESTDATA_U8));
+ data.writeByte(0);
+ data.writeByte(1);
+ data.writeByte(numeric_limits<int8_t>::max());
+ data.writeByteVector(i8s);
+ data.writeByteVector(u8s);
+ data.writeByteVector(optional<vector<int8_t>>({}));
+
+ android::Parcel reply;
+ ASSERT_EQ(m_server->transact(TEST_BYTE, data, &reply), OK);
+
+ vector<int8_t> read_i8s;
+ vector<uint8_t> read_u8s;
+ optional<vector<int8_t>> maybe_i8s;
+ ASSERT_EQ(reply.readByte(), 0);
+ ASSERT_EQ(reply.readByte(), 1);
+ ASSERT_EQ(reply.readByte(), numeric_limits<int8_t>::max());
+ ASSERT_EQ(reply.readByteVector(&read_i8s), OK);
+ ASSERT_EQ(read_i8s, i8s);
+ ASSERT_EQ(reply.readByteVector(&read_u8s), OK);
+ ASSERT_EQ(read_u8s, u8s);
+ ASSERT_EQ(reply.readByteVector(&maybe_i8s), OK);
+ ASSERT_EQ(maybe_i8s, nullopt);
+
+ int32_t end;
+ ASSERT_EQ(reply.readInt32(&end), NOT_ENOUGH_DATA);
+}
+
+TEST_F(SerializationTest, SerializeU16) {
+ android::Parcel data;
+ data.writeInterfaceToken(String16("read_parcel_test"));
+
+ vector<char16_t> chars(begin(TESTDATA_CHARS), end(TESTDATA_CHARS));
+ data.writeChar(0);
+ data.writeChar(1);
+ data.writeChar(numeric_limits<char16_t>::max());
+ data.writeCharVector(chars);
+ data.writeCharVector(nullopt);
+
+ android::Parcel reply;
+ ASSERT_EQ(m_server->transact(TEST_U16, data, &reply), OK);
+
+ vector<char16_t> read_chars;
+ optional<vector<char16_t>> maybe_chars;
+ ASSERT_EQ(reply.readChar(), 0);
+ ASSERT_EQ(reply.readChar(), 1);
+ ASSERT_EQ(reply.readChar(), numeric_limits<char16_t>::max());
+ ASSERT_EQ(reply.readCharVector(&read_chars), OK);
+ ASSERT_EQ(read_chars, chars);
+ ASSERT_EQ(reply.readCharVector(&maybe_chars), OK);
+ ASSERT_EQ(maybe_chars, nullopt);
+
+ int32_t end;
+ ASSERT_EQ(reply.readInt32(&end), NOT_ENOUGH_DATA);
+}
+
+TEST_F(SerializationTest, SerializeI32) {
+ android::Parcel data;
+ data.writeInterfaceToken(String16("read_parcel_test"));
+
+ vector<int32_t> i32s(begin(TESTDATA_I32), end(TESTDATA_I32));
+ data.writeInt32(0);
+ data.writeInt32(1);
+ data.writeInt32(numeric_limits<int32_t>::max());
+ data.writeInt32Vector(i32s);
+ data.writeInt32Vector(nullopt);
+
+ android::Parcel reply;
+ ASSERT_EQ(m_server->transact(TEST_I32, data, &reply), OK);
+
+ vector<int32_t> read_i32s;
+ optional<vector<int32_t>> maybe_i32s;
+ ASSERT_EQ(reply.readInt32(), 0);
+ ASSERT_EQ(reply.readInt32(), 1);
+ ASSERT_EQ(reply.readInt32(), numeric_limits<int32_t>::max());
+ ASSERT_EQ(reply.readInt32Vector(&read_i32s), OK);
+ ASSERT_EQ(read_i32s, i32s);
+ ASSERT_EQ(reply.readInt32Vector(&maybe_i32s), OK);
+ ASSERT_EQ(maybe_i32s, nullopt);
+
+ int32_t end;
+ ASSERT_EQ(reply.readInt32(&end), NOT_ENOUGH_DATA);
+}
+
+TEST_F(SerializationTest, SerializeI64) {
+ android::Parcel data;
+ data.writeInterfaceToken(String16("read_parcel_test"));
+
+ vector<int64_t> i64s(begin(TESTDATA_I64), end(TESTDATA_I64));
+ data.writeInt64(0);
+ data.writeInt64(1);
+ data.writeInt64(numeric_limits<int64_t>::max());
+ data.writeInt64Vector(i64s);
+ data.writeInt64Vector(nullopt);
+
+ android::Parcel reply;
+ ASSERT_EQ(m_server->transact(TEST_I64, data, &reply), OK);
+
+ vector<int64_t> read_i64s;
+ optional<vector<int64_t>> maybe_i64s;
+ ASSERT_EQ(reply.readInt64(), 0);
+ ASSERT_EQ(reply.readInt64(), 1);
+ ASSERT_EQ(reply.readInt64(), numeric_limits<int64_t>::max());
+ ASSERT_EQ(reply.readInt64Vector(&read_i64s), OK);
+ ASSERT_EQ(read_i64s, i64s);
+ ASSERT_EQ(reply.readInt64Vector(&maybe_i64s), OK);
+ ASSERT_EQ(maybe_i64s, nullopt);
+
+ int32_t end;
+ ASSERT_EQ(reply.readInt32(&end), NOT_ENOUGH_DATA);
+}
+
+TEST_F(SerializationTest, SerializeU64) {
+ android::Parcel data;
+ data.writeInterfaceToken(String16("read_parcel_test"));
+
+ vector<uint64_t> u64s(begin(TESTDATA_U64), end(TESTDATA_U64));
+ data.writeUint64(0);
+ data.writeUint64(1);
+ data.writeUint64(numeric_limits<uint64_t>::max());
+ data.writeUint64Vector(u64s);
+ data.writeUint64Vector(nullopt);
+
+ android::Parcel reply;
+ ASSERT_EQ(m_server->transact(TEST_U64, data, &reply), OK);
+
+ vector<uint64_t> read_u64s;
+ optional<vector<uint64_t>> maybe_u64s;
+ ASSERT_EQ(reply.readUint64(), 0);
+ ASSERT_EQ(reply.readUint64(), 1);
+ ASSERT_EQ(reply.readUint64(), numeric_limits<uint64_t>::max());
+ ASSERT_EQ(reply.readUint64Vector(&read_u64s), OK);
+ ASSERT_EQ(read_u64s, u64s);
+ ASSERT_EQ(reply.readUint64Vector(&maybe_u64s), OK);
+ ASSERT_EQ(maybe_u64s, nullopt);
+
+ int32_t end;
+ ASSERT_EQ(reply.readInt32(&end), NOT_ENOUGH_DATA);
+}
+
+TEST_F(SerializationTest, SerializeF32) {
+ android::Parcel data;
+ data.writeInterfaceToken(String16("read_parcel_test"));
+
+ vector<float> floats(begin(TESTDATA_FLOAT), end(TESTDATA_FLOAT));
+ data.writeFloat(0);
+ data.writeFloatVector(floats);
+ data.writeFloatVector(nullopt);
+
+ android::Parcel reply;
+ ASSERT_EQ(m_server->transact(TEST_F32, data, &reply), OK);
+
+ vector<float> read_floats;
+ optional<vector<float>> maybe_floats;
+ ASSERT_EQ(reply.readFloat(), 0);
+ ASSERT_EQ(reply.readFloatVector(&read_floats), OK);
+ ASSERT_TRUE(isnan(read_floats[0]));
+ ASSERT_EQ(read_floats[1], floats[1]);
+ ASSERT_EQ(read_floats[2], floats[2]);
+ ASSERT_EQ(read_floats[3], floats[3]);
+ ASSERT_EQ(reply.readFloatVector(&maybe_floats), OK);
+ ASSERT_EQ(maybe_floats, nullopt);
+
+ int32_t end;
+ ASSERT_EQ(reply.readInt32(&end), NOT_ENOUGH_DATA);
+}
+
+TEST_F(SerializationTest, SerializeF64) {
+ android::Parcel data;
+ data.writeInterfaceToken(String16("read_parcel_test"));
+
+ vector<double> doubles(begin(TESTDATA_DOUBLE), end(TESTDATA_DOUBLE));
+ data.writeDouble(0);
+ data.writeDoubleVector(doubles);
+ data.writeDoubleVector(nullopt);
+
+ android::Parcel reply;
+ ASSERT_EQ(m_server->transact(TEST_F64, data, &reply), OK);
+
+ vector<double> read_doubles;
+ optional<vector<double>> maybe_doubles;
+ ASSERT_EQ(reply.readDouble(), 0);
+ ASSERT_EQ(reply.readDoubleVector(&read_doubles), OK);
+ ASSERT_TRUE(isnan(read_doubles[0]));
+ ASSERT_EQ(read_doubles[1], doubles[1]);
+ ASSERT_EQ(read_doubles[2], doubles[2]);
+ ASSERT_EQ(read_doubles[3], doubles[3]);
+ ASSERT_EQ(reply.readDoubleVector(&maybe_doubles), OK);
+ ASSERT_EQ(maybe_doubles, nullopt);
+
+ int32_t end;
+ ASSERT_EQ(reply.readInt32(&end), NOT_ENOUGH_DATA);
+}
+
+TEST_F(SerializationTest, SerializeString) {
+ android::Parcel data;
+ data.writeInterfaceToken(String16("read_parcel_test"));
+
+ vector<optional<String16>> strings;
+ for (auto I = begin(TESTDATA_STRS), E = end(TESTDATA_STRS); I != E; ++I) {
+ if (*I == nullptr) {
+ strings.push_back(optional<String16>());
+ } else {
+ strings.emplace_back(*I);
+ }
+ }
+ data.writeUtf8AsUtf16(string("testing"));
+ data.writeString16(nullopt);
+ data.writeString16Vector(strings);
+ data.writeString16Vector(nullopt);
+
+ android::Parcel reply;
+ ASSERT_EQ(m_server->transact(TEST_STRING, data, &reply), OK);
+
+ optional<String16> maybe_string;
+ optional<vector<optional<String16>>> read_strings;
+ ASSERT_EQ(reply.readString16(), String16("testing"));
+ ASSERT_EQ(reply.readString16(&maybe_string), OK);
+ ASSERT_EQ(maybe_string, nullopt);
+ ASSERT_EQ(reply.readString16Vector(&read_strings), OK);
+ ASSERT_EQ(read_strings, strings);
+ ASSERT_EQ(reply.readString16Vector(&read_strings), OK);
+ ASSERT_EQ(read_strings, nullopt);
+
+ int32_t end;
+ ASSERT_EQ(reply.readInt32(&end), NOT_ENOUGH_DATA);
+}
+
+TEST_F(SerializationTest, SerializeFileDescriptor) {
+ unique_fd out_file, in_file;
+ ASSERT_TRUE(base::Pipe(&out_file, &in_file));
+
+ vector<ParcelFileDescriptor> file_descriptors;
+ file_descriptors.push_back(ParcelFileDescriptor(std::move(out_file)));
+ file_descriptors.push_back(ParcelFileDescriptor(std::move(in_file)));
+
+ android::Parcel data;
+ data.writeInterfaceToken(String16("read_parcel_test"));
+
+ data.writeParcelable(file_descriptors[0]);
+ data.writeParcelable(file_descriptors[1]);
+ data.writeParcelableVector(file_descriptors);
+
+ android::Parcel reply;
+ ASSERT_EQ(m_server->transact(TEST_FILE_DESCRIPTOR, data, &reply), OK);
+
+ ParcelFileDescriptor returned_fd1, returned_fd2;
+ vector<ParcelFileDescriptor> returned_file_descriptors;
+ ASSERT_EQ(reply.readParcelable(&returned_fd1), OK);
+ ASSERT_EQ(reply.readParcelable(&returned_fd2), OK);
+ ASSERT_EQ(reply.readParcelableVector(&returned_file_descriptors), OK);
+
+ int32_t end;
+ ASSERT_EQ(reply.readInt32(&end), NOT_ENOUGH_DATA);
+
+ base::WriteStringToFd("Testing", returned_fd2.get());
+ base::WriteStringToFd("File", returned_file_descriptors[1].get());
+ base::WriteStringToFd("Descriptors", file_descriptors[1].get());
+
+ string expected = "TestingFileDescriptors";
+ vector<char> buf(expected.length());
+ base::ReadFully(file_descriptors[0].release(), buf.data(), buf.size());
+ ASSERT_EQ(expected, string(buf.data()));
+}
+
+TEST_F(SerializationTest, SerializeIBinder) {
+ android::Parcel data;
+ data.writeInterfaceToken(String16("read_parcel_test"));
+
+ data.writeStrongBinder(m_server);
+ data.writeStrongBinder(nullptr);
+ data.writeStrongBinderVector({m_server, nullptr});
+ data.writeStrongBinderVector(nullopt);
+
+ android::Parcel reply;
+ ASSERT_EQ(m_server->transact(TEST_IBINDER, data, &reply), OK);
+
+ optional<vector<sp<IBinder>>> binders;
+ ASSERT_TRUE(reply.readStrongBinder());
+ ASSERT_FALSE(reply.readStrongBinder());
+ ASSERT_EQ(reply.readStrongBinderVector(&binders), OK);
+ ASSERT_EQ(binders->size(), 2);
+ ASSERT_TRUE((*binders)[0]);
+ ASSERT_FALSE((*binders)[1]);
+ ASSERT_EQ(reply.readStrongBinderVector(&binders), OK);
+ ASSERT_FALSE(binders);
+
+ int32_t end;
+ ASSERT_EQ(reply.readInt32(&end), NOT_ENOUGH_DATA);
+}
+
+TEST_F(SerializationTest, SerializeStatus) {
+ android::Parcel data;
+ data.writeInterfaceToken(String16("read_parcel_test"));
+
+ binder::Status::ok().writeToParcel(&data);
+ binder::Status::fromExceptionCode(binder::Status::EX_NULL_POINTER, "a status message")
+ .writeToParcel(&data);
+ binder::Status::fromServiceSpecificError(42, "a service-specific error").writeToParcel(&data);
+
+ android::Parcel reply;
+ ASSERT_EQ(m_server->transact(TEST_STATUS, data, &reply), OK);
+
+ binder::Status status;
+
+ ASSERT_EQ(status.readFromParcel(reply), OK);
+ ASSERT_TRUE(status.isOk());
+
+ ASSERT_EQ(status.readFromParcel(reply), OK);
+ ASSERT_EQ(status.exceptionCode(), binder::Status::EX_NULL_POINTER);
+ ASSERT_EQ(status.exceptionMessage(), "a status message");
+
+ ASSERT_EQ(status.readFromParcel(reply), OK);
+ ASSERT_EQ(status.serviceSpecificErrorCode(), 42);
+ ASSERT_EQ(status.exceptionMessage(), "a service-specific error");
+
+ int32_t end;
+ ASSERT_EQ(reply.readInt32(&end), NOT_ENOUGH_DATA);
+}
+
+// Test that failures from Rust properly propagate to C++
+TEST_F(SerializationTest, SerializeRustFail) {
+ android::Parcel data;
+ data.writeInterfaceToken(String16("read_parcel_test"));
+ ASSERT_EQ(m_server->transact(TEST_FAIL, data, nullptr), FAILED_TRANSACTION);
+}
+
+int main(int argc, char **argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ gEnvironment = AddGlobalTestEnvironment(new SerializationEnvironment());
+ ProcessState::self()->startThreadPool();
+ return RUN_ALL_TESTS();
+}
diff --git a/libs/binder/rust/tests/serialization.hpp b/libs/binder/rust/tests/serialization.hpp
new file mode 100644
index 0000000..0041608
--- /dev/null
+++ b/libs/binder/rust/tests/serialization.hpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#pragma once
+
+#include <binder/IBinder.h>
+
+using namespace android;
+
+enum Transaction {
+ TEST_BOOL = IBinder::FIRST_CALL_TRANSACTION,
+ TEST_BYTE,
+ TEST_U16,
+ TEST_I32,
+ TEST_I64,
+ TEST_U64,
+ TEST_F32,
+ TEST_F64,
+ TEST_STRING,
+ TEST_FILE_DESCRIPTOR,
+ TEST_IBINDER,
+ TEST_STATUS,
+ TEST_FAIL,
+};
+
+extern const int8_t TESTDATA_I8[4];
+extern const uint8_t TESTDATA_U8[4];
+extern const char16_t TESTDATA_CHARS[4];
+extern const int32_t TESTDATA_I32[4];
+extern const int64_t TESTDATA_I64[4];
+extern const uint64_t TESTDATA_U64[4];
+extern const float TESTDATA_FLOAT[4];
+extern const double TESTDATA_DOUBLE[4];
+extern const bool TESTDATA_BOOL[4];
+extern const char* const TESTDATA_STRS[4];
diff --git a/libs/binder/rust/tests/serialization.rs b/libs/binder/rust/tests/serialization.rs
new file mode 100644
index 0000000..2ae13f4
--- /dev/null
+++ b/libs/binder/rust/tests/serialization.rs
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+//! Included as a module in the binder crate internal tests for internal API
+//! access.
+
+use binder::declare_binder_interface;
+use binder::{
+ Binder, ExceptionCode, Interface, Parcel, Result, SpIBinder, Status,
+ StatusCode, TransactionCode,
+};
+use binder::parcel::ParcelFileDescriptor;
+
+use std::ffi::{c_void, CStr, CString};
+use std::panic::{self, AssertUnwindSafe};
+use std::sync::Once;
+
+#[allow(
+ non_camel_case_types,
+ non_snake_case,
+ non_upper_case_globals,
+ unused,
+ improper_ctypes,
+ missing_docs,
+ clippy::all
+)]
+mod bindings {
+ include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+}
+
+static SERVICE_ONCE: Once = Once::new();
+static mut SERVICE: Option<SpIBinder> = None;
+
+/// Start binder service and return a raw AIBinder pointer to it.
+///
+/// Safe to call multiple times, only creates the service once.
+#[no_mangle]
+pub extern "C" fn rust_service() -> *mut c_void {
+ unsafe {
+ SERVICE_ONCE.call_once(|| {
+ SERVICE = Some(BnReadParcelTest::new_binder(()).as_binder());
+ });
+ SERVICE.as_ref().unwrap().as_raw().cast()
+ }
+}
+
+/// Empty interface just to use the declare_binder_interface macro
+pub trait ReadParcelTest: Interface {}
+
+declare_binder_interface! {
+ ReadParcelTest["read_parcel_test"] {
+ native: BnReadParcelTest(on_transact),
+ proxy: BpReadParcelTest,
+ }
+}
+
+impl ReadParcelTest for Binder<BnReadParcelTest> {}
+
+impl ReadParcelTest for BpReadParcelTest {}
+
+impl ReadParcelTest for () {}
+
+fn on_transact(
+ _service: &dyn ReadParcelTest,
+ code: TransactionCode,
+ parcel: &Parcel,
+ reply: &mut Parcel,
+) -> Result<()> {
+ panic::catch_unwind(AssertUnwindSafe(|| transact_inner(code, parcel, reply))).unwrap_or_else(
+ |e| {
+ eprintln!("Failure in Rust: {:?}", e.downcast_ref::<String>());
+ Err(StatusCode::FAILED_TRANSACTION)
+ },
+ )
+}
+
+#[allow(clippy::float_cmp)]
+fn transact_inner(code: TransactionCode, parcel: &Parcel, reply: &mut Parcel) -> Result<()> {
+ match code {
+ bindings::Transaction_TEST_BOOL => {
+ assert_eq!(parcel.read::<bool>()?, true);
+ assert_eq!(parcel.read::<bool>()?, false);
+ assert_eq!(parcel.read::<Vec<bool>>()?, unsafe {
+ bindings::TESTDATA_BOOL
+ });
+ assert_eq!(parcel.read::<Option<Vec<bool>>>()?, None);
+
+ reply.write(&true)?;
+ reply.write(&false)?;
+ reply.write(&unsafe { bindings::TESTDATA_BOOL }[..])?;
+ reply.write(&(None as Option<Vec<bool>>))?;
+ }
+ bindings::Transaction_TEST_BYTE => {
+ assert_eq!(parcel.read::<i8>()?, 0);
+ assert_eq!(parcel.read::<i8>()?, 1);
+ assert_eq!(parcel.read::<i8>()?, i8::max_value());
+ assert_eq!(parcel.read::<Vec<i8>>()?, unsafe { bindings::TESTDATA_I8 });
+ assert_eq!(parcel.read::<Vec<u8>>()?, unsafe { bindings::TESTDATA_U8 });
+ assert_eq!(parcel.read::<Option<Vec<i8>>>()?, None);
+
+ reply.write(&0i8)?;
+ reply.write(&1i8)?;
+ reply.write(&i8::max_value())?;
+ reply.write(&unsafe { bindings::TESTDATA_I8 }[..])?;
+ reply.write(&unsafe { bindings::TESTDATA_U8 }[..])?;
+ reply.write(&(None as Option<Vec<i8>>))?;
+ }
+ bindings::Transaction_TEST_U16 => {
+ assert_eq!(parcel.read::<u16>()?, 0);
+ assert_eq!(parcel.read::<u16>()?, 1);
+ assert_eq!(parcel.read::<u16>()?, u16::max_value());
+ assert_eq!(parcel.read::<Vec<u16>>()?, unsafe {
+ bindings::TESTDATA_CHARS
+ });
+ assert_eq!(parcel.read::<Option<Vec<u16>>>()?, None);
+
+ reply.write(&0u16)?;
+ reply.write(&1u16)?;
+ reply.write(&u16::max_value())?;
+ reply.write(&unsafe { bindings::TESTDATA_CHARS }[..])?;
+ reply.write(&(None as Option<Vec<u16>>))?;
+ }
+ bindings::Transaction_TEST_I32 => {
+ assert_eq!(parcel.read::<i32>()?, 0);
+ assert_eq!(parcel.read::<i32>()?, 1);
+ assert_eq!(parcel.read::<i32>()?, i32::max_value());
+ assert_eq!(parcel.read::<Vec<i32>>()?, unsafe {
+ bindings::TESTDATA_I32
+ });
+ assert_eq!(parcel.read::<Option<Vec<i32>>>()?, None);
+
+ reply.write(&0i32)?;
+ reply.write(&1i32)?;
+ reply.write(&i32::max_value())?;
+ reply.write(&unsafe { bindings::TESTDATA_I32 }[..])?;
+ reply.write(&(None as Option<Vec<i32>>))?;
+ }
+ bindings::Transaction_TEST_I64 => {
+ assert_eq!(parcel.read::<i64>()?, 0);
+ assert_eq!(parcel.read::<i64>()?, 1);
+ assert_eq!(parcel.read::<i64>()?, i64::max_value());
+ assert_eq!(parcel.read::<Vec<i64>>()?, unsafe {
+ bindings::TESTDATA_I64
+ });
+ assert_eq!(parcel.read::<Option<Vec<i64>>>()?, None);
+
+ reply.write(&0i64)?;
+ reply.write(&1i64)?;
+ reply.write(&i64::max_value())?;
+ reply.write(&unsafe { bindings::TESTDATA_I64 }[..])?;
+ reply.write(&(None as Option<Vec<i64>>))?;
+ }
+ bindings::Transaction_TEST_U64 => {
+ assert_eq!(parcel.read::<u64>()?, 0);
+ assert_eq!(parcel.read::<u64>()?, 1);
+ assert_eq!(parcel.read::<u64>()?, u64::max_value());
+ assert_eq!(parcel.read::<Vec<u64>>()?, unsafe {
+ bindings::TESTDATA_U64
+ });
+ assert_eq!(parcel.read::<Option<Vec<u64>>>()?, None);
+
+ reply.write(&0u64)?;
+ reply.write(&1u64)?;
+ reply.write(&u64::max_value())?;
+ reply.write(&unsafe { bindings::TESTDATA_U64 }[..])?;
+ reply.write(&(None as Option<Vec<u64>>))?;
+ }
+ bindings::Transaction_TEST_F32 => {
+ assert_eq!(parcel.read::<f32>()?, 0f32);
+ let floats = parcel.read::<Vec<f32>>()?;
+ assert!(floats[0].is_nan());
+ assert_eq!(floats[1..], unsafe { bindings::TESTDATA_FLOAT }[1..]);
+ assert_eq!(parcel.read::<Option<Vec<f32>>>()?, None);
+
+ reply.write(&0f32)?;
+ reply.write(&unsafe { bindings::TESTDATA_FLOAT }[..])?;
+ reply.write(&(None as Option<Vec<f32>>))?;
+ }
+ bindings::Transaction_TEST_F64 => {
+ assert_eq!(parcel.read::<f64>()?, 0f64);
+ let doubles = parcel.read::<Vec<f64>>()?;
+ assert!(doubles[0].is_nan());
+ assert_eq!(doubles[1..], unsafe { bindings::TESTDATA_DOUBLE }[1..]);
+ assert_eq!(parcel.read::<Option<Vec<f64>>>()?, None);
+
+ reply.write(&0f64)?;
+ reply.write(&unsafe { bindings::TESTDATA_DOUBLE }[..])?;
+ reply.write(&(None as Option<Vec<f64>>))?;
+ }
+ bindings::Transaction_TEST_STRING => {
+ let s: Option<String> = parcel.read()?;
+ assert_eq!(s.as_deref(), Some("testing"));
+ let s: Option<String> = parcel.read()?;
+ assert_eq!(s, None);
+ let s: Option<Vec<Option<String>>> = parcel.read()?;
+ for (s, expected) in s
+ .unwrap()
+ .iter()
+ .zip(unsafe { bindings::TESTDATA_STRS }.iter())
+ {
+ let expected = unsafe {
+ expected
+ .as_ref()
+ .and_then(|e| CStr::from_ptr(e).to_str().ok())
+ };
+ assert_eq!(s.as_deref(), expected);
+ }
+ let s: Option<Vec<Option<String>>> = parcel.read()?;
+ assert_eq!(s, None);
+
+ let strings: Vec<Option<String>> = unsafe {
+ bindings::TESTDATA_STRS
+ .iter()
+ .map(|s| {
+ s.as_ref().map(|s| {
+ CStr::from_ptr(s)
+ .to_str()
+ .expect("String was not UTF-8")
+ .to_owned()
+ })
+ })
+ .collect()
+ };
+
+ reply.write("testing")?;
+ reply.write(&(None as Option<String>))?;
+ reply.write(&strings)?;
+ reply.write(&(None as Option<Vec<String>>))?;
+ }
+ bindings::Transaction_TEST_FILE_DESCRIPTOR => {
+ let file1 = parcel.read::<ParcelFileDescriptor>()?;
+ let file2 = parcel.read::<ParcelFileDescriptor>()?;
+ let files = parcel.read::<Vec<Option<ParcelFileDescriptor>>>()?;
+
+ reply.write(&file1)?;
+ reply.write(&file2)?;
+ reply.write(&files)?;
+ }
+ bindings::Transaction_TEST_IBINDER => {
+ assert!(parcel.read::<Option<SpIBinder>>()?.is_some());
+ assert!(parcel.read::<Option<SpIBinder>>()?.is_none());
+ let ibinders = parcel.read::<Option<Vec<Option<SpIBinder>>>>()?.unwrap();
+ assert_eq!(ibinders.len(), 2);
+ assert!(ibinders[0].is_some());
+ assert!(ibinders[1].is_none());
+ assert!(parcel.read::<Option<Vec<Option<SpIBinder>>>>()?.is_none());
+
+ let service = unsafe {
+ SERVICE
+ .as_ref()
+ .expect("Global binder service not initialized")
+ .clone()
+ };
+ reply.write(&service)?;
+ reply.write(&(None as Option<&SpIBinder>))?;
+ reply.write(&[Some(&service), None][..])?;
+ reply.write(&(None as Option<Vec<Option<&SpIBinder>>>))?;
+ }
+ bindings::Transaction_TEST_STATUS => {
+ let status: Status = parcel.read()?;
+ assert!(status.is_ok());
+ let status: Status = parcel.read()?;
+ assert_eq!(status.exception_code(), ExceptionCode::NULL_POINTER);
+ assert_eq!(
+ status.get_description(),
+ "Status(-4, EX_NULL_POINTER): 'a status message'"
+ );
+ let status: Status = parcel.read()?;
+ assert_eq!(status.service_specific_error(), 42);
+ assert_eq!(
+ status.get_description(),
+ "Status(-8, EX_SERVICE_SPECIFIC): '42: a service-specific error'"
+ );
+
+ reply.write(&Status::ok())?;
+ reply.write(&Status::new_exception(
+ ExceptionCode::NULL_POINTER,
+ Some(&CString::new("a status message").unwrap()),
+ ))?;
+ reply.write(&Status::new_service_specific_error(
+ 42,
+ Some(&CString::new("a service-specific error").unwrap()),
+ ))?;
+ }
+ bindings::Transaction_TEST_FAIL => {
+ panic!("Testing expected failure");
+ }
+ _ => return Err(StatusCode::UNKNOWN_TRANSACTION),
+ }
+
+ assert_eq!(parcel.read::<i32>(), Err(StatusCode::NOT_ENOUGH_DATA));
+ Ok(())
+}
diff --git a/libs/binder/tests/binderClearBufTest.cpp b/libs/binder/tests/binderClearBufTest.cpp
index a565e72..2d30c8d 100644
--- a/libs/binder/tests/binderClearBufTest.cpp
+++ b/libs/binder/tests/binderClearBufTest.cpp
@@ -83,7 +83,6 @@
lastReply = reply.data();
lastReplySize = reply.dataSize();
}
- IPCThreadState::self()->flushCommands();
*outBuffer = hexString(lastReply, lastReplySize);
return result;
}