android-binder 0.2.1

Safe bindings to Android Binder, restricted to the NDK
/*
 * Copyright (C) 2022 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 binder::unstable_api::new_spibinder;
use binder::{FromIBinder, SpIBinder, StatusCode, Strong};
use binder_ndk_sys::uid_t;
use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
use std::os::fd::RawFd;
use std::os::raw::{c_int, c_void};

pub use binder_rpc_unstable_bindgen::ARpcSession_FileDescriptorTransportMode as FileDescriptorTransportMode;

foreign_type! {
    type CType = binder_rpc_unstable_bindgen::ARpcSession;
    fn drop = binder_rpc_unstable_bindgen::ARpcSession_free;

    /// A type that represents a foreign instance of RpcSession.
    #[derive(Debug)]
    pub struct RpcSession;
    /// A borrowed RpcSession.
    pub struct RpcSessionRef;
}

/// SAFETY: The opaque handle can be cloned freely.
unsafe impl Send for RpcSession {}
/// SAFETY: The underlying C++ RpcSession class is thread-safe.
unsafe impl Sync for RpcSession {}

impl RpcSession {
    /// Allocates a new RpcSession object.
    pub fn new() -> RpcSession {
        // SAFETY: Takes ownership of the returned handle, which has correct refcount.
        unsafe { RpcSession::from_ptr(binder_rpc_unstable_bindgen::ARpcSession_new()) }
    }
}

impl Default for RpcSession {
    fn default() -> Self {
        Self::new()
    }
}

impl RpcSessionRef {
    /// Fetch the UID (if present) of the client process for this session.
    pub fn get_client_uid(&self) -> Option<uid_t> {
        let mut uid: uid_t = 0;
        // SAFETY: The session object is guaranteed to be valid for the lifetime
        // of the RpcSessionRef. `uid` is a valid mutable reference.
        unsafe {
            binder_rpc_unstable_bindgen::ARpcSession_getClientUid(self.as_ptr(), &mut uid)
                .then_some(uid)
        }
    }

    /// Sets the file descriptor transport mode for this session.
    pub fn set_file_descriptor_transport_mode(&self, mode: FileDescriptorTransportMode) {
        // SAFETY: Only passes the 'self' pointer as an opaque handle.
        unsafe {
            binder_rpc_unstable_bindgen::ARpcSession_setFileDescriptorTransportMode(
                self.as_ptr(),
                mode,
            )
        };
    }

    /// Sets the maximum number of incoming threads.
    pub fn set_max_incoming_threads(&self, threads: usize) {
        // SAFETY: Only passes the 'self' pointer as an opaque handle.
        unsafe {
            binder_rpc_unstable_bindgen::ARpcSession_setMaxIncomingThreads(self.as_ptr(), threads)
        };
    }

    /// Sets the maximum number of outgoing connections.
    pub fn set_max_outgoing_connections(&self, connections: usize) {
        // SAFETY: Only passes the 'self' pointer as an opaque handle.
        unsafe {
            binder_rpc_unstable_bindgen::ARpcSession_setMaxOutgoingConnections(
                self.as_ptr(),
                connections,
            )
        };
    }

    /// Connects to an RPC Binder server over vsock for a particular interface.
    #[cfg(not(target_os = "trusty"))]
    pub fn setup_vsock_client<T: FromIBinder + ?Sized>(
        &self,
        cid: u32,
        port: u32,
    ) -> Result<Strong<T>, StatusCode> {
        // SAFETY: AIBinder returned by ARpcSession_setupVsockClient has correct
        // reference count, and the ownership can safely be taken by new_spibinder.
        let service = unsafe {
            new_spibinder(binder_rpc_unstable_bindgen::ARpcSession_setupVsockClient(
                self.as_ptr(),
                cid,
                port,
            ))
        };
        Self::get_interface(service)
    }

    /// Connects to an RPC Binder server over a names Unix Domain Socket for
    /// a particular interface.
    #[cfg(not(target_os = "trusty"))]
    pub fn setup_unix_domain_client<T: FromIBinder + ?Sized>(
        &self,
        socket_name: &str,
    ) -> Result<Strong<T>, StatusCode> {
        let socket_name = match std::ffi::CString::new(socket_name) {
            Ok(s) => s,
            Err(e) => {
                log::error!("Cannot convert {} to CString. Error: {:?}", socket_name, e);
                return Err(StatusCode::NAME_NOT_FOUND);
            }
        };

        // SAFETY: AIBinder returned by ARpcSession_setupUnixDomainClient has correct
        // reference count, and the ownership can safely be taken by new_spibinder.
        let service = unsafe {
            new_spibinder(binder_rpc_unstable_bindgen::ARpcSession_setupUnixDomainClient(
                self.as_ptr(),
                socket_name.as_ptr(),
            ))
        };
        Self::get_interface(service)
    }

    /// Connects to an RPC Binder server over a bootstrap Unix Domain Socket
    /// for a particular interface.
    #[cfg(not(target_os = "trusty"))]
    pub fn setup_unix_domain_bootstrap_client<T: FromIBinder + ?Sized>(
        &self,
        bootstrap_fd: std::os::fd::BorrowedFd,
    ) -> Result<Strong<T>, StatusCode> {
        use std::os::fd::AsRawFd;
        // SAFETY: ARpcSession_setupUnixDomainBootstrapClient does not take
        // ownership of bootstrap_fd. The returned AIBinder has correct
        // reference count, and the ownership can safely be taken by new_spibinder.
        let service = unsafe {
            new_spibinder(binder_rpc_unstable_bindgen::ARpcSession_setupUnixDomainBootstrapClient(
                self.as_ptr(),
                bootstrap_fd.as_raw_fd(),
            ))
        };
        Self::get_interface(service)
    }

    /// Connects to an RPC Binder server over inet socket at the given address and port.
    #[cfg(not(target_os = "trusty"))]
    pub fn setup_inet_client<T: FromIBinder + ?Sized>(
        &self,
        address: &str,
        port: u32,
    ) -> Result<Strong<T>, StatusCode> {
        let address = match std::ffi::CString::new(address) {
            Ok(s) => s,
            Err(e) => {
                log::error!("Cannot convert {} to CString. Error: {:?}", address, e);
                return Err(StatusCode::BAD_VALUE);
            }
        };

        // SAFETY: AIBinder returned by ARpcSession_setupInet has correct reference
        // count, and the ownership can safely be taken by new_spibinder.
        let service = unsafe {
            new_spibinder(binder_rpc_unstable_bindgen::ARpcSession_setupInet(
                self.as_ptr(),
                address.as_ptr(),
                port,
            ))
        };
        Self::get_interface(service)
    }

    #[cfg(target_os = "trusty")]
    pub fn setup_trusty_client<T: FromIBinder + ?Sized>(
        &self,
        port: &std::ffi::CStr,
    ) -> Result<Strong<T>, StatusCode> {
        self.setup_preconnected_client(|| {
            let h = tipc::Handle::connect(port)
                .expect("Failed to connect to service port {SERVICE_PORT}");

            // Do not close the handle at the end of the scope
            let fd = h.as_raw_fd();
            core::mem::forget(h);
            Some(fd)
        })
    }

    /// Connects to an RPC Binder server, using the given callback to get (and
    /// take ownership of) file descriptors already connected to it.
    pub fn setup_preconnected_client<T: FromIBinder + ?Sized>(
        &self,
        request_fd: impl FnMut() -> Option<RawFd>,
    ) -> Result<Strong<T>, StatusCode> {
        // Trait objects aren't FFI safe, so *mut c_void can't be converted back to
        // *mut dyn FnMut() -> Option<RawFd>>. Double box the factory to make it possible to get
        // the factory from *mut c_void (to *mut Box<dyn<...>>) in the callbacks.
        let request_fd_box: Box<dyn FnMut() -> Option<RawFd>> = Box::new(request_fd);
        let param = Box::into_raw(Box::new(request_fd_box)) as *mut c_void;

        // SAFETY: AIBinder returned by RpcPreconnectedClient has correct reference count, and the
        // ownership can be safely taken by new_spibinder. RpcPreconnectedClient does not take ownership
        // of param, only passing it to request_fd_wrapper.
        let service = unsafe {
            new_spibinder(binder_rpc_unstable_bindgen::ARpcSession_setupPreconnectedClient(
                self.as_ptr(),
                Some(request_fd_wrapper),
                param,
                Some(param_delete_fd_wrapper),
            ))
        };
        Self::get_interface(service)
    }

    fn get_interface<T: FromIBinder + ?Sized>(
        service: Option<SpIBinder>,
    ) -> Result<Strong<T>, StatusCode> {
        if let Some(service) = service {
            FromIBinder::try_from(service)
        } else {
            Err(StatusCode::NAME_NOT_FOUND)
        }
    }
}

unsafe extern "C" fn request_fd_wrapper(param: *mut c_void) -> c_int {
    let request_fd_ptr = param as *mut Box<dyn FnMut() -> Option<RawFd>>;
    // SAFETY: This is only ever called by RpcPreconnectedClient, within the lifetime of the
    // BinderFdFactory reference, with param being a properly aligned non-null pointer to an
    // initialized instance.
    let request_fd = unsafe { request_fd_ptr.as_mut().unwrap() };
    request_fd().unwrap_or(-1)
}

unsafe extern "C" fn param_delete_fd_wrapper(param: *mut c_void) {
    // SAFETY: This is only ever called by RpcPreconnectedClient, with param being the
    // pointer returned from Box::into_raw.
    let request_fd_box = unsafe { Box::from_raw(param as *mut Box<dyn FnMut() -> Option<RawFd>>) };
    drop(request_fd_box);
}