cloudfox-coreshift-core 2.32.0

Low-level Linux and Android systems primitives for CoreShift (CloudFox)
Documentation
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/

//! Generic binder client for any named Android service.

use super::sys::*;
use crate::CoreError;
use libc::c_void;
use std::ffi::CString;
use std::os::raw::c_char;

// ── Client-only class callbacks ───────────────────────────────────────────

// The NDK requires a class on ANY binder that participates in a transaction
// (AIBinder_prepareTransaction fails with STATUS_INVALID_OPERATION when
// getClass() == null) and writes the interface token from the class
// descriptor. These stubs are never invoked for a client (remote) binder —
// onCreate/onTransact only fire on local AIBinder_new instances — so they
// mirror the ActivityManager / DisplayManager client pattern exactly.
unsafe extern "C" fn raw_client_on_create(_: *mut c_void) -> *mut c_void {
    std::ptr::null_mut()
}
unsafe extern "C" fn raw_client_on_destroy(_: *mut c_void) {}
unsafe extern "C" fn raw_client_on_transact(
    _: *mut AIBinder,
    _: u32,
    _: *const AParcel,
    _: *mut AParcel,
) -> BinderStatus {
    STATUS_UNKNOWN_TRANSACTION
}

// ── RawBinderService ──────────────────────────────────────────────────────

/// Generic binder client for any named Android service.
///
/// Handles its own `dlopen` on `libbinder_ndk.so`. Callers provide raw
/// transaction codes (resolved via [`crate::android::dex::find_transaction_code`])
/// and use [`RawBinderService::transact_bool`] /
/// [`RawBinderService::transact_i32`] for typed round-trips.
pub struct RawBinderService {
    _lib: DlHandle,
    vt: Vtable,
    _class: *mut AIBinder_Class,
    // AOSP `AIBinder_Class` keeps the descriptor `const char*` without
    // copying, so the class descriptor must outlive the service handle.
    _desc: CString,
    service: OwnedBinder,
}
unsafe impl Send for RawBinderService {}

impl RawBinderService {
    /// Open a connection to the named service (e.g. `"audio"`), associating a
    /// client-only class whose descriptor is the service's AIDL interface
    /// (e.g. `"android.media.IAudioService"`).
    ///
    /// The class is required even for a pure client: `AIBinder_prepareTransaction`
    /// fails with `STATUS_INVALID_OPERATION` when `getClass() == null`, and it
    /// writes the interface token from the class descriptor, which the remote
    /// `enforceInterface` checks against its own descriptor.
    pub fn open(service_name: &str, descriptor: &str) -> Result<Self, CoreError> {
        let handle = unsafe {
            libc::dlopen(
                LIBBINDER_PATH.as_ptr() as *const c_char,
                libc::RTLD_NOW | libc::RTLD_LOCAL,
            )
        };
        if handle.is_null() {
            return Err(CoreError::binder(-1, "dlopen:libbinder_ndk.so"));
        }
        let lib = DlHandle;
        let vt = load_vtable(handle)?;
        let cs = CString::new(service_name)
            .map_err(|_| CoreError::binder(-1, "service_name:nul_byte"))?;
        let desc =
            CString::new(descriptor).map_err(|_| CoreError::binder(-1, "descriptor:nul_byte"))?;
        let raw = unsafe { (vt.get_service)(cs.as_ptr()) };
        if raw.is_null() {
            return Err(CoreError::binder(-1, "AServiceManager_getService:null"));
        }
        let class = unsafe {
            (vt.class_define)(
                desc.as_ptr(),
                raw_client_on_create,
                raw_client_on_destroy,
                raw_client_on_transact,
            )
        };
        if class.is_null() {
            return Err(CoreError::binder(-1, "AIBinder_Class_define:client"));
        }
        unsafe { (vt.associate_class)(raw, class) };
        let service = OwnedBinder {
            ptr: raw,
            dec_strong: vt.dec_strong,
        };
        Ok(Self {
            _lib: lib,
            vt,
            _class: class,
            _desc: desc,
            service,
        })
    }

    /// Send a no-argument transaction; read exception header then bool reply.
    pub fn transact_bool(&self, code: u32) -> Result<bool, CoreError> {
        let out = self.raw_noarg(code)?;
        let r = ParcelReader::owned(&self.vt, &out);
        let ex = r.read_i32()?;
        if ex != EX_NONE {
            return Err(CoreError::binder(ex, "transact_bool:exception"));
        }
        if let Some(rb) = self.vt.read_bool {
            let mut v = false;
            let s = unsafe { rb(out.ptr as *const AParcel, &mut v) };
            if s != STATUS_OK {
                return Err(CoreError::binder(s, "AParcel_readBool"));
            }
            Ok(v)
        } else {
            Ok(r.read_i32()? != 0)
        }
    }

    /// Send a transaction with one i32 argument; discard reply.
    pub fn transact_i32(&self, code: u32, arg: i32) -> Result<(), CoreError> {
        let _ = transact_write(&self.vt, self.service.ptr, code, |w| w.write_i32(arg))?;
        Ok(())
    }

    /// Send a transaction with one boolean argument (`writeInt(0|1)` — the
    /// AOSP `Parcel.writeBoolean` wire encoding); discard reply. Used for
    /// one-way enforcement flags such as
    /// `INetworkPolicyManager.setDeviceIdleMode(boolean)`.
    pub fn transact_bool_arg(&self, code: u32, value: bool) -> Result<(), CoreError> {
        self.transact_i32(code, if value { 1 } else { 0 })
    }

    pub(super) fn raw_noarg(&self, code: u32) -> Result<OwnedParcel, CoreError> {
        transact_write(&self.vt, self.service.ptr, code, |_| Ok(()))
    }

    /// Expose the loaded vtable for reply decoding (probe.rs builds a
    /// `ParcelReader` over the raw reply).
    pub(super) fn vtable(&self) -> &Vtable {
        &self.vt
    }
}