/*
 * 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.
 */

//! Rust Binder crate integration tests

use binder::{declare_binder_enum, declare_binder_interface};
use binder::{BinderFeatures, Interface, StatusCode, ThreadState};
// Import from internal API for testing only, do not use this module in
// production.
use binder::binder_impl::{
    Binder, BorrowedParcel, IBinderInternal, TransactionCode, FIRST_CALL_TRANSACTION,
};

use std::convert::{TryFrom, TryInto};
use std::ffi::CStr;
use std::fs::File;
use std::sync::Mutex;

/// Name of service runner.
///
/// Must match the binary name in Android.bp
const RUST_SERVICE_BINARY: &str = "rustBinderTestService";

/// Binary to run a test service.
///
/// This needs to be in a separate process from the tests, so we spawn this
/// binary as a child, providing the service name as an argument.
fn main() -> Result<(), &'static str> {
    // Ensure that we can handle all transactions on the main thread.
    binder::ProcessState::set_thread_pool_max_thread_count(0);
    binder::ProcessState::start_thread_pool();

    let mut args = std::env::args().skip(1);
    if args.len() < 1 || args.len() > 2 {
        print_usage();
        return Err("");
    }
    let service_name = args.next().ok_or_else(|| {
        print_usage();
        "Missing SERVICE_NAME argument"
    })?;
    let extension_name = args.next();

    {
        let mut service = Binder::new(BnTest(Box::new(TestService::new(&service_name))));
        service.set_requesting_sid(true);
        if let Some(extension_name) = extension_name {
            let extension =
                BnTest::new_binder(TestService::new(&extension_name), BinderFeatures::default());
            service.set_extension(&mut extension.as_binder()).expect("Could not add extension");
        }
        binder::add_service(&service_name, service.as_binder())
            .expect("Could not register service");
    }

    binder::ProcessState::join_thread_pool();
    Err("Unexpected exit after join_thread_pool")
}

fn print_usage() {
    eprintln!("Usage: {} SERVICE_NAME [EXTENSION_NAME]", RUST_SERVICE_BINARY);
    eprintln!(concat!(
        "Spawn a Binder test service identified by SERVICE_NAME,",
        " optionally with an extesion named EXTENSION_NAME",
    ));
}

struct TestService {
    s: String,
    dump_args: Mutex<Vec<String>>,
}

impl TestService {
    fn new(s: &str) -> Self {
        Self { s: s.to_string(), dump_args: Mutex::new(Vec::new()) }
    }
}

#[repr(u32)]
enum TestTransactionCode {
    Test = FIRST_CALL_TRANSACTION,
    GetDumpArgs,
    GetSelinuxContext,
    GetIsHandlingTransaction,
}

impl TryFrom<u32> for TestTransactionCode {
    type Error = StatusCode;

    fn try_from(c: u32) -> Result<Self, Self::Error> {
        match c {
            _ if c == TestTransactionCode::Test as u32 => Ok(TestTransactionCode::Test),
            _ if c == TestTransactionCode::GetDumpArgs as u32 => {
                Ok(TestTransactionCode::GetDumpArgs)
            }
            _ if c == TestTransactionCode::GetSelinuxContext as u32 => {
                Ok(TestTransactionCode::GetSelinuxContext)
            }
            _ if c == TestTransactionCode::GetIsHandlingTransaction as u32 => {
                Ok(TestTransactionCode::GetIsHandlingTransaction)
            }
            _ => Err(StatusCode::UNKNOWN_TRANSACTION),
        }
    }
}

impl Interface for TestService {
    fn dump(&self, _file: &File, args: &[&CStr]) -> Result<(), StatusCode> {
        let mut dump_args = self.dump_args.lock().unwrap();
        dump_args.extend(args.iter().map(|s| s.to_str().unwrap().to_owned()));
        Ok(())
    }
}

impl ITest for TestService {
    fn test(&self) -> Result<String, StatusCode> {
        Ok(self.s.clone())
    }

    fn get_dump_args(&self) -> Result<Vec<String>, StatusCode> {
        let args = self.dump_args.lock().unwrap().clone();
        Ok(args)
    }

    fn get_selinux_context(&self) -> Result<String, StatusCode> {
        let sid =
            ThreadState::with_calling_sid(|sid| sid.map(|s| s.to_string_lossy().into_owned()));
        sid.ok_or(StatusCode::UNEXPECTED_NULL)
    }

    fn get_is_handling_transaction(&self) -> Result<bool, StatusCode> {
        Ok(binder::is_handling_transaction())
    }
}

/// Trivial testing binder interface
pub trait ITest: Interface {
    /// Returns a test string
    fn test(&self) -> Result<String, StatusCode>;

    /// Return the arguments sent via dump
    fn get_dump_args(&self) -> Result<Vec<String>, StatusCode>;

    /// Returns the caller's SELinux context
    fn get_selinux_context(&self) -> Result<String, StatusCode>;

    /// Returns the value of calling `is_handling_transaction`.
    fn get_is_handling_transaction(&self) -> Result<bool, StatusCode>;
}

/// Async trivial testing binder interface
pub trait IATest<P>: Interface {
    /// Returns a test string
    fn test(&self) -> binder::BoxFuture<'static, Result<String, StatusCode>>;

    /// Return the arguments sent via dump
    fn get_dump_args(&self) -> binder::BoxFuture<'static, Result<Vec<String>, StatusCode>>;

    /// Returns the caller's SELinux context
    fn get_selinux_context(&self) -> binder::BoxFuture<'static, Result<String, StatusCode>>;

    /// Returns the value of calling `is_handling_transaction`.
    fn get_is_handling_transaction(&self) -> binder::BoxFuture<'static, Result<bool, StatusCode>>;
}

declare_binder_interface! {
    ITest["android.os.ITest"] {
        native: BnTest(on_transact),
        proxy: BpTest {
            x: i32 = 100
        },
        async: IATest,
    }
}

fn on_transact(
    service: &dyn ITest,
    code: TransactionCode,
    _data: &BorrowedParcel<'_>,
    reply: &mut BorrowedParcel<'_>,
) -> Result<(), StatusCode> {
    match code.try_into()? {
        TestTransactionCode::Test => reply.write(&service.test()?),
        TestTransactionCode::GetDumpArgs => reply.write(&service.get_dump_args()?),
        TestTransactionCode::GetSelinuxContext => reply.write(&service.get_selinux_context()?),
        TestTransactionCode::GetIsHandlingTransaction => {
            reply.write(&service.get_is_handling_transaction()?)
        }
    }
}

impl ITest for BpTest {
    fn test(&self) -> Result<String, StatusCode> {
        let reply =
            self.binder.transact(TestTransactionCode::Test as TransactionCode, 0, |_| Ok(()))?;
        reply.read()
    }

    fn get_dump_args(&self) -> Result<Vec<String>, StatusCode> {
        let reply =
            self.binder
                .transact(TestTransactionCode::GetDumpArgs as TransactionCode, 0, |_| Ok(()))?;
        reply.read()
    }

    fn get_selinux_context(&self) -> Result<String, StatusCode> {
        let reply = self.binder.transact(
            TestTransactionCode::GetSelinuxContext as TransactionCode,
            0,
            |_| Ok(()),
        )?;
        reply.read()
    }

    fn get_is_handling_transaction(&self) -> Result<bool, StatusCode> {
        let reply = self.binder.transact(
            TestTransactionCode::GetIsHandlingTransaction as TransactionCode,
            0,
            |_| Ok(()),
        )?;
        reply.read()
    }
}

impl<P: binder::BinderAsyncPool> IATest<P> for BpTest {
    fn test(&self) -> binder::BoxFuture<'static, Result<String, StatusCode>> {
        let binder = self.binder.clone();
        P::spawn(
            move || binder.transact(TestTransactionCode::Test as TransactionCode, 0, |_| Ok(())),
            |reply| async move { reply?.read() },
        )
    }

    fn get_dump_args(&self) -> binder::BoxFuture<'static, Result<Vec<String>, StatusCode>> {
        let binder = self.binder.clone();
        P::spawn(
            move || {
                binder.transact(TestTransactionCode::GetDumpArgs as TransactionCode, 0, |_| Ok(()))
            },
            |reply| async move { reply?.read() },
        )
    }

    fn get_selinux_context(&self) -> binder::BoxFuture<'static, Result<String, StatusCode>> {
        let binder = self.binder.clone();
        P::spawn(
            move || {
                binder.transact(
                    TestTransactionCode::GetSelinuxContext as TransactionCode,
                    0,
                    |_| Ok(()),
                )
            },
            |reply| async move { reply?.read() },
        )
    }

    fn get_is_handling_transaction(&self) -> binder::BoxFuture<'static, Result<bool, StatusCode>> {
        let binder = self.binder.clone();
        P::spawn(
            move || {
                binder.transact(
                    TestTransactionCode::GetIsHandlingTransaction as TransactionCode,
                    0,
                    |_| Ok(()),
                )
            },
            |reply| async move { reply?.read() },
        )
    }
}

impl ITest for Binder<BnTest> {
    fn test(&self) -> Result<String, StatusCode> {
        self.0.test()
    }

    fn get_dump_args(&self) -> Result<Vec<String>, StatusCode> {
        self.0.get_dump_args()
    }

    fn get_selinux_context(&self) -> Result<String, StatusCode> {
        self.0.get_selinux_context()
    }

    fn get_is_handling_transaction(&self) -> Result<bool, StatusCode> {
        self.0.get_is_handling_transaction()
    }
}

impl<P: binder::BinderAsyncPool> IATest<P> for Binder<BnTest> {
    fn test(&self) -> binder::BoxFuture<'static, Result<String, StatusCode>> {
        let res = self.0.test();
        Box::pin(async move { res })
    }

    fn get_dump_args(&self) -> binder::BoxFuture<'static, Result<Vec<String>, StatusCode>> {
        let res = self.0.get_dump_args();
        Box::pin(async move { res })
    }

    fn get_selinux_context(&self) -> binder::BoxFuture<'static, Result<String, StatusCode>> {
        let res = self.0.get_selinux_context();
        Box::pin(async move { res })
    }

    fn get_is_handling_transaction(&self) -> binder::BoxFuture<'static, Result<bool, StatusCode>> {
        let res = self.0.get_is_handling_transaction();
        Box::pin(async move { res })
    }
}

/// Trivial testing binder interface
pub trait ITestSameDescriptor: Interface {}

declare_binder_interface! {
    ITestSameDescriptor["android.os.ITest"] {
        native: BnTestSameDescriptor(on_transact_same_descriptor),
        proxy: BpTestSameDescriptor,
    }
}

fn on_transact_same_descriptor(
    _service: &dyn ITestSameDescriptor,
    _code: TransactionCode,
    _data: &BorrowedParcel<'_>,
    _reply: &mut BorrowedParcel<'_>,
) -> Result<(), StatusCode> {
    Ok(())
}

impl ITestSameDescriptor for BpTestSameDescriptor {}

impl ITestSameDescriptor for Binder<BnTestSameDescriptor> {}

declare_binder_enum! {
    TestEnum : [i32; 3] {
        FOO = 1,
        BAR = 2,
        BAZ = 3,
    }
}

declare_binder_enum! {
    #[deprecated(since = "1.0.0")]
    TestDeprecatedEnum : [i32; 3] {
        FOO = 1,
        BAR = 2,
        BAZ = 3,
    }
}

#[cfg(test)]
mod tests {
    use selinux_bindgen as selinux_sys;
    use std::ffi::CStr;
    use std::fs::File;
    use std::process::{Child, Command};
    use std::ptr;
    use std::sync::atomic::{AtomicBool, Ordering};
    use std::sync::Arc;
    use std::thread;
    use std::time::Duration;

    use binder::{
        BinderFeatures, DeathRecipient, FromIBinder, IBinder, Interface, SpIBinder, StatusCode,
        Strong,
    };
    // Import from impl API for testing only, should not be necessary as long as
    // you are using AIDL.
    use binder::binder_impl::{Binder, IBinderInternal, TransactionCode};

    use binder_tokio::Tokio;

    use super::{BnTest, IATest, ITest, ITestSameDescriptor, TestService, RUST_SERVICE_BINARY};

    pub struct ScopedServiceProcess(Child);

    impl ScopedServiceProcess {
        pub fn new(identifier: &str) -> Self {
            Self::new_internal(identifier, None)
        }

        pub fn new_with_extension(identifier: &str, extension: &str) -> Self {
            Self::new_internal(identifier, Some(extension))
        }

        fn new_internal(identifier: &str, extension: Option<&str>) -> Self {
            let mut binary_path =
                std::env::current_exe().expect("Could not retrieve current executable path");
            binary_path.pop();
            binary_path.push(RUST_SERVICE_BINARY);
            let mut command = Command::new(&binary_path);
            command.arg(identifier);
            if let Some(ext) = extension {
                command.arg(ext);
            }
            let child = command.spawn().expect("Could not start service");
            Self(child)
        }
    }

    impl Drop for ScopedServiceProcess {
        fn drop(&mut self) {
            self.0.kill().expect("Could not kill child process");
            self.0.wait().expect("Could not wait for child process to die");
        }
    }

    #[test]
    fn check_services() {
        let mut sm = binder::get_service("manager").expect("Did not get manager binder service");
        assert!(sm.is_binder_alive());
        assert!(sm.ping_binder().is_ok());

        assert!(binder::get_service("this_service_does_not_exist").is_none());
        assert_eq!(
            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)
        );
    }

    #[tokio::test]
    async fn check_services_async() {
        let mut sm = binder::get_service("manager").expect("Did not get manager binder service");
        assert!(sm.is_binder_alive());
        assert!(sm.ping_binder().is_ok());

        assert!(binder::get_service("this_service_does_not_exist").is_none());
        assert_eq!(
            binder_tokio::get_interface::<dyn ITest>("this_service_does_not_exist").await.err(),
            Some(StatusCode::NAME_NOT_FOUND)
        );
        assert_eq!(
            binder_tokio::get_interface::<dyn IATest<Tokio>>("this_service_does_not_exist")
                .await
                .err(),
            Some(StatusCode::NAME_NOT_FOUND)
        );

        // The service manager service isn't an ITest, so this must fail.
        assert_eq!(
            binder_tokio::get_interface::<dyn ITest>("manager").await.err(),
            Some(StatusCode::BAD_TYPE)
        );
        assert_eq!(
            binder_tokio::get_interface::<dyn IATest<Tokio>>("manager").await.err(),
            Some(StatusCode::BAD_TYPE)
        );
    }

    #[test]
    fn check_wait_for_service() {
        let mut sm =
            binder::wait_for_service("manager").expect("Did not get manager binder service");
        assert!(sm.is_binder_alive());
        assert!(sm.ping_binder().is_ok());

        // The service manager service isn't an ITest, so this must fail.
        assert_eq!(
            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]
    fn get_declared_instances() {
        // At the time of writing this test, there is no good VINTF interface
        // guaranteed to be on all devices. Cuttlefish has light, so this will
        // generally test things.
        let has_lights = binder::is_declared("android.hardware.light.ILights/default")
            .expect("Could not check for declared interface");

        let instances = binder::get_declared_instances("android.hardware.light.ILights")
            .expect("Could not get declared instances");

        let expected_defaults = usize::from(has_lights);
        assert_eq!(expected_defaults, instances.iter().filter(|i| i.as_str() == "default").count());
    }

    #[test]
    fn trivial_client() {
        let service_name = "trivial_client_test";
        let _process = ScopedServiceProcess::new(service_name);
        let test_client: Strong<dyn ITest> =
            binder::get_interface(service_name).expect("Did not get manager binder service");
        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_tokio::get_interface(service_name)
            .await
            .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";
        let _process = ScopedServiceProcess::new(service_name);
        let test_client: Strong<dyn ITest> =
            binder::wait_for_interface(service_name).expect("Did not get manager binder service");
        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_tokio::wait_for_interface(service_name)
            .await
            .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 {
        // SAFETY: The pointer we pass to `getcon` is valid because it comes from a reference, and
        // `getcon` doesn't retain it after it returns. If `getcon` succeeds then `out_ptr` will
        // point to a valid C string, otherwise it will remain null. We check for null, so the
        // pointer we pass to `CStr::from_ptr` must be a valid pointer to a C string. There is a
        // memory leak as we don't call `freecon`, but that's fine because this is just a test.
        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");
        assert_eq!(test_client.get_selinux_context().unwrap(), get_expected_selinux_context());
    }

    #[tokio::test]
    async fn get_selinux_context_async() {
        let service_name = "get_selinux_context_async";
        let _process = ScopedServiceProcess::new(service_name);
        let test_client: Strong<dyn IATest<Tokio>> = binder_tokio::get_interface(service_name)
            .await
            .expect("Did not get manager binder service");
        assert_eq!(
            test_client.get_selinux_context().await.unwrap(),
            get_expected_selinux_context()
        );
    }

    #[tokio::test]
    async fn get_selinux_context_sync_to_async() {
        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 test_client = test_client.into_async::<Tokio>();
        assert_eq!(
            test_client.get_selinux_context().await.unwrap(),
            get_expected_selinux_context()
        );
    }

    #[tokio::test]
    async fn get_selinux_context_async_to_sync() {
        let service_name = "get_selinux_context";
        let _process = ScopedServiceProcess::new(service_name);
        let test_client: Strong<dyn IATest<Tokio>> = binder_tokio::get_interface(service_name)
            .await
            .expect("Did not get manager binder service");
        let test_client = test_client.into_sync();
        assert_eq!(test_client.get_selinux_context().unwrap(), get_expected_selinux_context());
    }

    struct Bools {
        binder_died: Arc<AtomicBool>,
        binder_dealloc: Arc<AtomicBool>,
    }

    impl Bools {
        fn is_dead(&self) -> bool {
            self.binder_died.load(Ordering::Relaxed)
        }
        fn assert_died(&self) {
            assert!(self.is_dead(), "Did not receive death notification");
        }
        fn assert_dropped(&self) {
            assert!(
                self.binder_dealloc.load(Ordering::Relaxed),
                "Did not dealloc death notification"
            );
        }
        fn assert_not_dropped(&self) {
            assert!(
                !self.binder_dealloc.load(Ordering::Relaxed),
                "Dealloc death notification too early"
            );
        }
    }

    fn register_death_notification(binder: &mut SpIBinder) -> (Bools, DeathRecipient) {
        let binder_died = Arc::new(AtomicBool::new(false));
        let binder_dealloc = Arc::new(AtomicBool::new(false));

        struct SetOnDrop {
            binder_dealloc: Arc<AtomicBool>,
        }
        impl Drop for SetOnDrop {
            fn drop(&mut self) {
                self.binder_dealloc.store(true, Ordering::Relaxed);
            }
        }

        let mut death_recipient = {
            let flag = binder_died.clone();
            let set_on_drop = SetOnDrop { binder_dealloc: binder_dealloc.clone() };
            DeathRecipient::new(move || {
                flag.store(true, Ordering::Relaxed);
                // Force the closure to take ownership of set_on_drop. When the closure is
                // dropped, the destructor of `set_on_drop` will run.
                let _ = &set_on_drop;
            })
        };

        binder.link_to_death(&mut death_recipient).expect("link_to_death failed");

        let bools = Bools { binder_died, binder_dealloc };

        (bools, death_recipient)
    }

    /// Killing a remote service should unregister the service and trigger
    /// death notifications.
    #[test]
    fn test_death_notifications() {
        binder::ProcessState::start_thread_pool();

        let service_name = "test_death_notifications";
        let service_process = ScopedServiceProcess::new(service_name);
        let mut remote = binder::get_service(service_name).expect("Could not retrieve service");

        let (bools, recipient) = register_death_notification(&mut remote);

        drop(service_process);
        remote.ping_binder().expect_err("Service should have died already");

        // Pause to ensure any death notifications get delivered
        thread::sleep(Duration::from_secs(1));

        bools.assert_died();
        bools.assert_not_dropped();

        drop(recipient);

        bools.assert_dropped();
    }

    /// Test unregistering death notifications.
    #[test]
    fn test_unregister_death_notifications() {
        binder::ProcessState::start_thread_pool();

        let service_name = "test_unregister_death_notifications";
        let service_process = ScopedServiceProcess::new(service_name);
        let mut remote = binder::get_service(service_name).expect("Could not retrieve service");

        let (bools, mut recipient) = register_death_notification(&mut remote);

        remote.unlink_to_death(&mut recipient).expect("Could not unlink death notifications");

        drop(service_process);
        remote.ping_binder().expect_err("Service should have died already");

        // Pause to ensure any death notifications get delivered
        thread::sleep(Duration::from_secs(1));

        assert!(!bools.is_dead(), "Received unexpected death notification after unlinking",);

        bools.assert_not_dropped();
        drop(recipient);
        bools.assert_dropped();
    }

    /// Dropping a remote handle should unregister any death notifications.
    #[test]
    fn test_death_notification_registration_lifetime() {
        binder::ProcessState::start_thread_pool();

        let service_name = "test_death_notification_registration_lifetime";
        let service_process = ScopedServiceProcess::new(service_name);
        let mut remote = binder::get_service(service_name).expect("Could not retrieve service");

        let (bools, recipient) = register_death_notification(&mut remote);

        // This should automatically unregister our death notification.
        drop(remote);

        drop(service_process);

        // Pause to ensure any death notifications get delivered
        thread::sleep(Duration::from_secs(1));

        // We dropped the remote handle, so we should not receive the death
        // notification when the remote process dies here.
        assert!(
            !bools.is_dead(),
            "Received unexpected death notification after dropping remote handle"
        );

        bools.assert_not_dropped();
        drop(recipient);
        bools.assert_dropped();
    }

    /// Test IBinder interface methods not exercised elsewhere.
    #[test]
    fn test_misc_ibinder() {
        let service_name = "rust_test_ibinder";

        {
            let _process = ScopedServiceProcess::new(service_name);

            let test_client: Strong<dyn ITest> =
                binder::get_interface(service_name).expect("Did not get test binder service");
            let mut remote = test_client.as_binder();
            assert!(remote.is_binder_alive());
            remote.ping_binder().expect("Could not ping remote service");

            let dump_args = ["dump", "args", "for", "testing"];

            let null_out = File::open("/dev/null").expect("Could not open /dev/null");
            remote.dump(&null_out, &dump_args).expect("Could not dump remote service");

            let remote_args = test_client.get_dump_args().expect("Could not fetched dumped args");
            assert_eq!(dump_args, remote_args[..], "Remote args don't match call to dump");
        }

        // get/set_extensions is tested in test_extensions()

        // transact is tested everywhere else, and we can't make raw
        // transactions outside the [FIRST_CALL_TRANSACTION,
        // LAST_CALL_TRANSACTION] range from the NDK anyway.

        // link_to_death is tested in test_*_death_notification* tests.
    }

    #[test]
    fn test_extensions() {
        let service_name = "rust_test_extensions";
        let extension_name = "rust_test_extensions_ext";

        {
            let _process = ScopedServiceProcess::new(service_name);

            let mut remote = binder::get_service(service_name);
            assert!(remote.is_binder_alive());

            let extension = remote.get_extension().expect("Could not check for an extension");
            assert!(extension.is_none());
        }

        {
            let _process = ScopedServiceProcess::new_with_extension(service_name, extension_name);

            let mut remote = binder::get_service(service_name);
            assert!(remote.is_binder_alive());

            let maybe_extension = remote.get_extension().expect("Could not check for an extension");

            let extension = maybe_extension.expect("Remote binder did not have an extension");

            let extension: Strong<dyn ITest> = FromIBinder::try_from(extension)
                .expect("Extension could not be converted to the expected interface");

            assert_eq!(extension.test().unwrap(), extension_name);
        }
    }

    /// Test re-associating a local binder object with a different class.
    ///
    /// This is needed because different binder service (e.g. NDK vs Rust)
    /// implementations are incompatible and must not be interchanged. A local
    /// service with the same descriptor string but a different class pointer
    /// may have been created by an NDK service and is therefore incompatible
    /// with the Rust service implementation. It must be treated as remote and
    /// all API calls parceled and sent through transactions.
    ///
    /// Further tests of this behavior with the C NDK and Rust API are in
    /// rust_ndk_interop.rs
    #[test]
    fn associate_existing_class() {
        let service = Binder::new(BnTest(Box::new(TestService::new("testing_service"))));

        // This should succeed although we will have to treat the service as
        // remote.
        let _interface: Strong<dyn ITestSameDescriptor> =
            FromIBinder::try_from(service.as_binder())
                .expect("Could not re-interpret service as the ITestSameDescriptor interface");
    }

    /// Test that we can round-trip a rust service through a generic IBinder
    #[test]
    fn reassociate_rust_binder() {
        let service_name = "testing_service";
        let service_ibinder =
            BnTest::new_binder(TestService::new(service_name), BinderFeatures::default())
                .as_binder();

        let service: Strong<dyn ITest> =
            service_ibinder.into_interface().expect("Could not reassociate the generic ibinder");

        assert_eq!(service.test().unwrap(), service_name);
    }

    #[test]
    fn weak_binder_upgrade() {
        let service_name = "testing_service";
        let service = BnTest::new_binder(TestService::new(service_name), BinderFeatures::default());

        let weak = Strong::downgrade(&service);

        let upgraded = weak.upgrade().expect("Could not upgrade weak binder");

        assert_eq!(service, upgraded);
    }

    #[test]
    fn weak_binder_upgrade_dead() {
        let service_name = "testing_service";
        let weak = {
            let service =
                BnTest::new_binder(TestService::new(service_name), BinderFeatures::default());

            Strong::downgrade(&service)
        };

        assert_eq!(weak.upgrade(), Err(StatusCode::DEAD_OBJECT));
    }

    #[test]
    fn weak_binder_clone() {
        let service_name = "testing_service";
        let service = BnTest::new_binder(TestService::new(service_name), BinderFeatures::default());

        let weak = Strong::downgrade(&service);
        let cloned = weak.clone();
        assert_eq!(weak, cloned);

        let upgraded = weak.upgrade().expect("Could not upgrade weak binder");
        let clone_upgraded = cloned.upgrade().expect("Could not upgrade weak binder");

        assert_eq!(service, upgraded);
        assert_eq!(service, clone_upgraded);
    }

    #[test]
    #[allow(clippy::eq_op)]
    fn binder_ord() {
        let service1 =
            BnTest::new_binder(TestService::new("testing_service1"), BinderFeatures::default());
        let service2 =
            BnTest::new_binder(TestService::new("testing_service2"), BinderFeatures::default());

        assert!((service1 >= service1));
        assert!((service1 <= service1));
        assert_eq!(service1 < service2, (service2 >= service1));
    }

    #[test]
    fn binder_parcel_mixup() {
        let service1 =
            BnTest::new_binder(TestService::new("testing_service1"), BinderFeatures::default());
        let service2 =
            BnTest::new_binder(TestService::new("testing_service2"), BinderFeatures::default());

        let service1 = service1.as_binder();
        let service2 = service2.as_binder();

        let parcel = service1.prepare_transact().unwrap();
        let res = service2.submit_transact(
            super::TestTransactionCode::Test as TransactionCode,
            parcel,
            0,
        );

        match res {
            Ok(_) => panic!("submit_transact should fail"),
            Err(err) => assert_eq!(err, binder::StatusCode::BAD_VALUE),
        }
    }

    #[test]
    fn get_is_handling_transaction() {
        let service_name = "get_is_handling_transaction";
        let _process = ScopedServiceProcess::new(service_name);
        let test_client: Strong<dyn ITest> =
            binder::get_interface(service_name).expect("Did not get manager binder service");
        // Should be true externally.
        assert!(test_client.get_is_handling_transaction().unwrap());

        // Should be false locally.
        assert!(!binder::is_handling_transaction());

        // Should also be false in spawned thread.
        std::thread::spawn(|| {
            assert!(!binder::is_handling_transaction());
        })
        .join()
        .unwrap();
    }

    #[tokio::test]
    async fn get_is_handling_transaction_async() {
        let service_name = "get_is_handling_transaction_async";
        let _process = ScopedServiceProcess::new(service_name);
        let test_client: Strong<dyn IATest<Tokio>> = binder_tokio::get_interface(service_name)
            .await
            .expect("Did not get manager binder service");
        // Should be true externally.
        assert!(test_client.get_is_handling_transaction().await.unwrap());

        // Should be false locally.
        assert!(!binder::is_handling_transaction());

        // Should also be false in spawned task.
        tokio::spawn(async {
            assert!(!binder::is_handling_transaction());
        })
        .await
        .unwrap();

        // And in spawn_blocking task.
        tokio::task::spawn_blocking(|| {
            assert!(!binder::is_handling_transaction());
        })
        .await
        .unwrap();
    }
}
