#![cfg(all(
feature = "rpc",
any(target_os = "linux", target_os = "android", target_os = "macos")
))]
use std::sync::Arc;
use std::thread;
use rsbinder::rpc::{FileDescriptorTransportMode as FdMode, RpcServer, RpcSession};
use rsbinder::shared_memory::{
export_heap, BpMemory, BpMemoryHeap, HeapCache, IMemory, IMemoryHeap, MemoryBase, MemoryDealer,
MemoryHeapBase, FLAG_READ_ONLY,
};
use rsbinder::{
Binder, Interface, Parcel, Remotable, Result as RsResult, StatusCode, TransactionCode,
FIRST_CALL_TRANSACTION,
};
fn page() -> usize {
rustix::param::page_size()
}
struct Bound {
server: Arc<RpcServer>,
#[cfg(not(target_os = "android"))]
path: std::path::PathBuf,
#[cfg(target_os = "android")]
name: Vec<u8>,
}
impl Bound {
fn new(tag: &str) -> Self {
let uniq = format!(
"rsb_shm_{}_{}_{}",
tag,
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
);
#[cfg(not(target_os = "android"))]
{
let mut path = std::env::temp_dir();
path.push(format!("{uniq}.sock"));
let server = RpcServer::setup_unix_server(&path).expect("bind");
Self { server, path }
}
#[cfg(target_os = "android")]
{
let name = uniq.into_bytes();
let server = RpcServer::setup_unix_server_abstract(&name).expect("bind abstract");
server.set_android13plus(1);
Self { server, name }
}
}
fn run(&self) -> thread::JoinHandle<()> {
let bg = self.server.run_background();
#[cfg(not(target_os = "android"))]
for _ in 0..400 {
if self.path.exists() {
break;
}
thread::sleep(std::time::Duration::from_millis(5));
}
bg
}
fn connect(&self, fd_mode: bool) -> RpcSession {
#[cfg(not(target_os = "android"))]
{
let client = RpcSession::setup_unix_client(&self.path).expect("connect");
if fd_mode {
assert_eq!(
client.negotiate_fd_transport(FdMode::Unix).unwrap(),
FdMode::Unix
);
}
client
}
#[cfg(target_os = "android")]
{
use rsbinder::rpc::RpcUnixClientConfig;
let mut cfg = RpcUnixClientConfig::abstract_name(&self.name, 1);
if fd_mode {
cfg = cfg.fd_mode(FdMode::Unix);
}
RpcSession::setup_unix_client_android13plus_with_config(cfg).expect("connect abstract")
}
}
fn finish(self, bg: thread::JoinHandle<()>) {
self.server.stop_accepting();
let _ = bg.join();
#[cfg(not(target_os = "android"))]
let _ = std::fs::remove_file(&self.path);
}
}
struct Served {
heap: Arc<MemoryHeapBase>,
window_offset: usize,
window_size: usize,
root: rsbinder::SIBinder,
}
fn serve(flags: u32) -> Served {
let heap = Arc::new(MemoryHeapBase::new(page() * 4, flags).unwrap());
let heap_binder = export_heap(heap.clone());
let window_offset = page();
let window_size = page() * 2;
let mem =
Arc::new(MemoryBase::new(heap.clone(), heap_binder, window_offset, window_size).unwrap());
Served {
heap,
window_offset,
window_size,
root: mem.export(),
}
}
#[test]
fn imemory_window_roundtrips_over_uds_with_fd_mode() {
let bound = Bound::new("ok");
bound.server.set_supported_fd_modes(&[FdMode::Unix]);
let served = serve(0);
bound
.server
.set_root(served.root.clone())
.expect("set_root");
let bg = bound.run();
let client = bound.connect(true);
let root = client.get_root().expect("get_root");
served
.heap
.write_at(served.window_offset + 16, b"server->client")
.unwrap();
let bp = BpMemory::new(root);
let heap = bp.resolve().expect("GET_MEMORY + HEAP_ID + mmap");
assert_eq!(bp.offset(), served.window_offset);
assert_eq!(bp.size(), served.window_size);
assert_eq!(heap.size(), page() * 4, "heap geometry travelled intact");
assert_eq!(heap.flags(), 0);
assert_eq!(IMemory::memory(&bp).size(), page() * 4);
let mut buf = [0u8; 14];
bp.read_at(16, &mut buf).unwrap();
assert_eq!(&buf, b"server->client");
bp.write_at(page(), b"client->server").unwrap();
let mut back = [0u8; 14];
served
.heap
.read_at(served.window_offset + page(), &mut back)
.unwrap();
assert_eq!(&back, b"client->server");
assert_eq!(
bp.read_at(served.window_size - 1, &mut [0u8; 2])
.unwrap_err(),
StatusCode::BadValue
);
let again = bp.resolve().unwrap();
assert!(Arc::ptr_eq(&heap, &again));
drop(bp);
drop(client);
bound.finish(bg);
}
#[test]
fn read_only_heap_is_read_only_at_the_client() {
let bound = Bound::new("ro");
bound.server.set_supported_fd_modes(&[FdMode::Unix]);
let served = serve(FLAG_READ_ONLY);
served.heap.write_at(served.window_offset, b"ro").unwrap();
bound
.server
.set_root(served.root.clone())
.expect("set_root");
let bg = bound.run();
let client = bound.connect(true);
let bp = BpMemory::new(client.get_root().unwrap());
let heap = bp.resolve().unwrap();
assert_eq!(heap.flags(), FLAG_READ_ONLY);
let seals = heap.map().unwrap().seals().expect("protections readable");
assert_ne!(seals & rsbinder::shared_memory::SEAL_FUTURE_WRITE, 0);
let mut b = [0u8; 2];
bp.read_at(0, &mut b).unwrap();
assert_eq!(&b, b"ro");
assert_eq!(
bp.write_at(0, b"xx").unwrap_err(),
StatusCode::PermissionDenied
);
drop(bp);
drop(client);
bound.finish(bg);
}
#[test]
fn bare_imemoryheap_root_maps() {
let bound = Bound::new("heap");
bound.server.set_supported_fd_modes(&[FdMode::Unix]);
let heap = Arc::new(MemoryHeapBase::new(page(), 0).unwrap());
bound
.server
.set_root(export_heap(heap.clone()))
.expect("set_root");
let bg = bound.run();
let client = bound.connect(true);
let bp = BpMemoryHeap::new(client.get_root().unwrap());
assert_eq!(bp.size(), 0, "not yet mapped");
let m = bp.map().unwrap();
assert_eq!(bp.size(), page());
heap.write_at(0, b"bare").unwrap();
let mut b = [0u8; 4];
m.read_at(0, &mut b).unwrap();
assert_eq!(&b, b"bare");
let head = bp.base().unwrap().slice(0, 4).unwrap().to_vec();
assert_eq!(head, b"bare");
drop(m);
drop(bp);
drop(client);
bound.finish(bg);
}
#[test]
fn heap_fd_rejected_without_fd_mode() {
let bound = Bound::new("nofd");
let served = serve(0);
bound
.server
.set_root(served.root.clone())
.expect("set_root");
let bg = bound.run();
let client = bound.connect(false);
let bp = BpMemory::new(client.get_root().unwrap());
let err = bp.resolve().unwrap_err();
assert!(
matches!(
err,
StatusCode::FdsNotAllowed | StatusCode::FailedTransaction
),
"unexpected error {err:?}"
);
assert_eq!(bp.size(), 0);
drop(bp);
drop(client);
bound.finish(bg);
}
struct BnAllocs(Vec<rsbinder::shared_memory::Allocation>);
impl Remotable for BnAllocs {
fn descriptor() -> &'static str {
"rsbinder.test.IAllocs"
}
fn on_transact(
&self,
code: TransactionCode,
reader: &mut Parcel,
reply: &mut Parcel,
) -> RsResult<()> {
match code {
FIRST_CALL_TRANSACTION => {
let i: i32 = reader.read()?;
let a = self.0.get(i as usize).ok_or(StatusCode::BadValue)?;
reply.write(&a.export())
}
_ => Err(StatusCode::UnknownTransaction),
}
}
fn on_dump(&self, _: &mut dyn std::io::Write, _: &[String]) -> RsResult<()> {
Ok(())
}
}
#[test]
fn dealer_allocations_share_one_mapping_through_heap_cache() {
let bound = Bound::new("dealer");
bound.server.set_supported_fd_modes(&[FdMode::Unix]);
let dealer = MemoryDealer::new(page() * 4, 0).unwrap();
let allocs: Vec<_> = (0..3)
.map(|i| {
let a = dealer.allocate(page() / 2 + i * 64).unwrap();
a.write_at(0, format!("block-{i}").as_bytes()).unwrap();
a
})
.collect();
let offsets: Vec<usize> = allocs.iter().map(|a| a.offset()).collect();
bound
.server
.set_root(Interface::as_binder(&Binder::new(BnAllocs(allocs))))
.expect("set_root");
let bg = bound.run();
let client = bound.connect(true);
let root = client.get_root().unwrap();
let rp = (*root)
.as_any()
.downcast_ref::<rsbinder::rpc::RpcProxy>()
.expect("RpcProxy");
let cache = HeapCache::new();
let mut heaps = Vec::new();
for i in 0..3i32 {
let mut data = rp.build_request("rsbinder.test.IAllocs").unwrap();
data.write(&i).unwrap();
let mut reply = rp
.transact(FIRST_CALL_TRANSACTION, &data, 0)
.unwrap()
.unwrap();
let mem_binder: rsbinder::SIBinder = reply.read().unwrap();
let bp = BpMemory::new_with_cache(mem_binder, cache.clone());
let heap = bp.resolve().unwrap();
assert_eq!(bp.offset(), offsets[i as usize]);
let mut buf = [0u8; 7];
bp.read_at(0, &mut buf).unwrap();
assert_eq!(&buf, format!("block-{i}").as_bytes());
heaps.push((bp, heap));
}
assert!(Arc::ptr_eq(&heaps[0].1, &heaps[1].1));
assert!(Arc::ptr_eq(&heaps[1].1, &heaps[2].1));
assert_eq!(cache.len(), 1);
assert_eq!(heaps[0].1.size(), page() * 4);
heaps[2].0.write_at(8, b"from-client").unwrap();
let mut back = [0u8; 11];
dealer.heap().read_at(offsets[2] + 8, &mut back).unwrap();
assert_eq!(&back, b"from-client");
drop(heaps);
assert!(cache.is_empty(), "weak entries are pruned once unused");
drop(client);
bound.finish(bg);
}