Overview

Welcome to rsbinder!

rsbinder is a Rust library and toolset that enables you to utilize Binder IPC on Linux and Android OS.

Binder IPC is an object-oriented IPC (Inter-Process Communication) mechanism that Google added to the Linux kernel for Android. Android uses Binder IPC for all process communication, and since 2015, it has been integrated into the Linux kernel, making it available on all Linux systems.

However, since it is rarely used outside of Android, it is disabled by default in most Linux distributions.

Key Features of Binder IPC:

  • Object-oriented: Binder IPC provides a clean and intuitive object-oriented API for inter-process communication.
  • Efficient: Binder IPC is designed for high performance and low overhead.
  • Secure: Binder IPC provides strong security features to prevent unauthorized access and tampering.
  • Versatile: Binder IPC can be used for a variety of purposes, including remote procedure calls, data sharing, and event notification.

Before using Binder IPC, you must enable the feature in the kernel. Please refer to Enable binder for Linux for detailed instructions on setting it up.

Architecture

---
title: Binder IPC Architecture
---
flowchart BT
    AIDL
    G[[Generated Rust Code
    for Service and Client]]
    S(Your Binder Service)
    C(Binder Client)
    H(HUB
    Service Manager)

    AIDL-->|rsbinder-aidl compiler|G;
    G-.->|Include|S;
    G-.->|Include|C;
    S-.->|Register Service|H;
    C-.->|Query Service|H;
    C<-->|Communication|S;

Description of each component of the diagram

  • AIDL
    • The Android Interface Definition Language (AIDL) is a tool that lets users abstract away IPC. Given an interface (specified in a .aidl file), various build systems use the rsbinder-aidl crate to construct Rust bindings so that this interface can be used across processes, regardless of the runtime or bitness there.
    • https://source.android.com/docs/core/architecture/aidl
  • Your Binder Service
    • Include the Rust code generated from AIDL to create your service.
    • Use rsbinder::hub to register your service with the HUB and wait for client requests.
  • Binder Client
    • Use the Rust code generated from AIDL to communicate with the service.
    • Query the HUB to check if the required service is available and receive a Binder Object (rsbinder::SIBinder) for the service.
    • Use the Binder Object to communicate with the Binder Service.
  • HUB(Service Manager)
    • rsbinder provides rsb_hub, a service manager for Linux.
    • On Android, no additional work is required since the Android service manager is already running.

Getting Started

If you are new to Binder IPC, read the Architecture document first.

The Architecture document provides a comprehensive overview of Binder IPC, including its design, implementation, and usage. It will help you understand the basics of Binder IPC before you start using rsbinder.

Installation

Enable binder for Linux

Please refer to Enable binder for Linux for detailed instructions on setting it up.

Create binder device file for Linux

After the binder configuration of the Linux kernel is complete, a binder device file must be created. Install rsbinder-tools and run to create a binder device.

$ cargo install rsbinder-tools
$ sudo rsb_device binder

Run a service manager for Linux

If rsbinder-tools is already installed, the rsb_hub executable is also installed. Let's run it as follows.

$ rsb_hub

Dependecies of rsbinder

Add the following configuration to your Cargo.toml file:

[dependencies]
rsbinder = "0.2.0"
lazy_static = "1"
async-trait = "0.1"

[build-dependencies]
rsbinder-aidl = "0.2.0"

The crates purposes:

  • rsbinder: This library provides various functionalities for Binder IPC, including communication with the Linux kernel and data serialization/deserialization.
  • lazy_static: The code generated by the rsbinder-aidl compiler depends on the lazy_static crate.
  • async-trait: The code generated by rsbinder-aidl creates both sync and async code. The async code depends on the async-trait crate.
  • rsbinder-aidl: This is used for AIDL compilation and is invoked in build.rs.

Hello World!

This tutorial will guide you through creating a simple Binder service that echoes a string back to the client, and a client program that uses the service.

Create a new Rust project

Create a new Rust project using Cargo:

$ cargo new --lib hello

Create a library project for the common library used by both the client and service:

Modify Cargo.toml

In the hello project's Cargo.toml, add the following dependencies:

[package]
name = "hello"
version = "0.1.0"
publish = false
edition = "2021"

[dependencies]
rsbinder = "0.2.0"
lazy_static = "1"
async-trait = "0.1"
env_logger = "0.11"

[build-dependencies]
rsbinder-aidl = "0.2.0"

Add rsbinder, lazy_static, and async-trait to [dependencies], and add rsbinder-aidl to [build-dependencies].

Create a AIDL File

Create an aidl folder in the project's top directory to manage AIDL files:

$ mkdir -p aidl/hello
$ touch IHello.aidl

The reason for creating an additional hello folder is to create a space for the hello package.

Create an IHello.aidl file with the following contents:

package hello;

// Defining the IHello Interface
interface IHello {
    // Defining the echo() Function.
    // The function takes a single parameter of type String and returns a value of type String.
    String echo(in String hello);
}

For more information on AIDL syntax, refer to the Android AIDL documentation.

Create the build.rs

Create a build.rs file to compile the AIDL file and generate Rust code:

use std::path::PathBuf;

fn main() {
    rsbinder_aidl::Builder::new()
        .source(PathBuf::from("aidl/hello/IHello.aidl"))
        .output(PathBuf::from("hello.rs"))
        .generate().unwrap();
}

This uses rsbinder-aidl to specify the AIDL source file("IHello.aidl") and the generated Rust file name("hello.rs"), and then generates the code.

Create a common library for Client and Service

For the Client and Service, create a library that includes the Rust code generated from AIDL.

Create src/lib.rs and add the following content.

// Include the code hello.rs generated from AIDL.
include!(concat!(env!("OUT_DIR"), "/hello.rs"));

// Set up to use the APIs provided in the code generated for Client and Service.
pub use crate::hello::IHello::*;

// Define the name of the service to be registered in the HUB(service manager).
pub const SERVICE_NAME: &str = "my.hello";

Create a service

Let's configure the src/bin/hello_service.rs file as follows.

use env_logger::Env;
use rsbinder::*;

use hello::*;

// Define the name of the service to be registered in the HUB(service manager).
struct IHelloService;

// Implement the IHello interface for the IHelloService.
impl Interface for IHelloService {
    // Reimplement the dump method. This is optional.
    fn dump(&self, writer: &mut dyn std::io::Write, _args: &[String]) -> Result<()> {
        writeln!(writer, "Dump IHelloService")?;
        Ok(())
    }
}

// Implement the IHello interface for the IHelloService.
impl IHello for IHelloService {
    // Implement the echo method.
    fn echo(&self, echo: &str) -> rsbinder::status::Result<String> {
        Ok(echo.to_owned())
    }
}

fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
    env_logger::Builder::from_env(Env::default().default_filter_or("warn")).init();

    // Initialize ProcessState with the default binder path and the default max threads.
    ProcessState::init_default();

    // Start the thread pool.
    // This is optional. If you don't call this, only one thread will be created to handle the binder transactions.
    ProcessState::start_thread_pool();

    // Create a binder service.
    let service = BnHello::new_binder(IHelloService{});

    // Add the service to binder service manager.
    hub::add_service(SERVICE_NAME, service.as_binder())?;

    // Join the thread pool.
    // This is a blocking call. It will return when the thread pool is terminated.
    Ok(ProcessState::join_thread_pool()?)
}

Create a client

Create the src/bin/hello_client.rs file and configure it as follows.

use env_logger::Env;
use rsbinder::*;
use hello::*;

fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
    env_logger::Builder::from_env(Env::default().default_filter_or("warn")).init();

    // Initialize ProcessState with the default binder path and the default max threads.
    ProcessState::init_default();

    // Create a Hello proxy from binder service manager.
    let hello: rsbinder::Strong<dyn IHello> = hub::get_interface(SERVICE_NAME)
        .expect(&format!("Can't find {SERVICE_NAME}"));

    // Call echo method of Hello proxy.
    println!("Result: {}", hello.echo("Hello World!")?);

    Ok(())
}

Project folder and file structure

.
├── Cargo.toml
├── aidl
│   └── hello
│       └── IHello.aidl
├── build.rs
└── src
    ├── bin
    │   ├── hello_client.rs
    │   └── hello_service.rs
    └── lib.rs

Run Hello Service and Client

  • Run hello_service
$ cargo run --bin hello_service
  • Run hello_client
$ cargo run --bin hello_client

Enable binder for Linux

Most Linux distributions do not have Binder IPC enabled by default, so additional steps are required to use it.

If you are able to build the Linux kernel yourself, you can enable Binder IPC by adding the following kernel configuration options:

CONFIG_ASHMEM=y
CONFIG_ANDROID=y
CONFIG_ANDROID_BINDER_IPC=y
CONFIG_ANDROID_BINDERFS=y

Let's examine methods to activate Binder IPC in some representative Linux distributions.

Enable Binder IPC on Arch Linux

Let's install the linux-zen kernel provided by Arch Linux. Zen kernel already includes Binder IPC.

$ pacman -S linux-zen

For detailed instructions on installing a new kernel in Arch Linux, refer to the following URL.

https://wiki.archlinux.org/title/kernel

rsbinder supports not only Linux but also Android. Since Android already has an environment prepared for binder communication, rsbinder, rsbinder-aidl, and rsbinder-hub are utilized for development. There is no need to create a binder device or run a service manager, so rsbinder-tools are not used.

For building in the Android environment, it is necessary to install the NDK, and an additional Rust build environment that utilizes the NDK is required.

Android Build

Compatibility with Android Versions

There are compatibility issues with Binder IPC depending on the Android version. The current findings indicate there are compatibility issues before and after Android 12.

If your software needs to work on both Android 11 and 12, you must set the Android version using the rsbinder::set_android_version() API.

The Android Version information can be checked and set as follows.

use android_system_properties::AndroidSystemProperties;

let properties = AndroidSystemProperties::new();

if let Some(version) = properties.get("ro.build.version.release") {
    rsbinder::set_android_version(version.parse())
}

Android Build

NDK Install

The NDK can be installed using Android Studio, or it can be downloaded and installed from the following URL.

Cargo NDK Install

To build rsbinder using the NDK, the installation of cargo-ndk is required.

Build rsbinder with NDK

After installing cargo-ndk, various targets were installed with $ rustup target add. Use this target information to build with the following command.

$ cargo ndk -t x86_64-linux-android build