feat(transport/ble): add an embedder-supplied radio backend for Android

Every backend so far owns a Bluetooth stack in process: BluerIo talks to
BlueZ over D-Bus and MockBleIo is an in-memory double. Android allows
neither. Scanning, advertising, L2CAP listen and connect, and the socket I/O
itself are Java APIs held under a permission and foreground-service model that
only the application can satisfy, so there is no Rust-reachable radio to open.

This backend therefore drives the embedder's radio rather than one of its own.
The embedder implements AndroidRadio and installs it into a BleRadioSlot; the
backend resolves that slot per operation, so a radio that arrives after the
node is built is adopted in place instead of needing the node rebuilt around
it, and one that is cleared parks the backend rather than failing it. The slot
is per-node rather than a process global, because a global collapses as soon as
two nodes share a process, and installing twice returns the existing slot
rather than replacing it, because a slot may already hold a live radio that a
second call must not orphan.

Android assigns the L2CAP PSM rather than letting the application request one,
which is what the PSM seam exists for: listen reports back whatever the
platform bound, and that is the value advertised. A listener that fails to open
is not latched, so a later resolve can retry it.

The module is compiled under cfg(test) on every host as well as on the platform
that selects it, so its channel machinery, slot semantics and connect routing
are exercised by an ordinary test run on an ordinary runner. The platform build
is linted but executed nowhere, which is precisely why the logic must not sit
behind a platform-only gate.

It lands as io_android.rs, beside io_linux.rs, so each backend is one file
named for its platform and everything platform-neutral stays in io.rs and the
modules beside it. Adding macOS should mean adding io_macos.rs and nothing
else.

Nothing here is reachable yet: no build selects this backend and nothing
installs a radio. Both arrive in the following commit.
This commit is contained in:
Arjen
2026-08-26 07:58:58 +01:00
committed by Johnathan Corgan
parent 901947899f
commit 6867a290a4
2 changed files with 1720 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+10
View File
@@ -29,6 +29,16 @@
pub mod addr;
pub mod io;
/// A backend whose radio is supplied by the embedder rather than opened in
/// process.
///
/// Compiled under `cfg(test)` on every host as well as on the platform that
/// will select it, so its channel machinery, slot semantics and connect
/// routing are exercised by an ordinary test run on an ordinary runner. The
/// platform build of it is linted but executed nowhere, which is exactly why
/// the logic must not be behind a platform-only gate.
#[cfg(any(target_os = "android", test))]
pub mod io_android;
#[cfg(bluer_available)]
pub mod io_linux;
pub mod neighbor;