Files
fips/build.rs
ArjenandJohnathan Corgan 58eae46c16 feat(ble): add a CoreBluetooth backend for macOS
macOS now peers over BLE with Linux, Android and other Macs when the
daemon runs in a login session with Bluetooth permission.

CoreBluetooth is delegate-driven and moves channel bytes through
Foundation streams, the same command-out, callback-in shape as the
Android embedder's radio, so the backend plays the embedder's part in
process rather than implementing BleIo: MacRadio drives CoreBluetooth
from a private serial dispatch queue (no main run loop), installs a fresh
bridge into its slot each time Bluetooth comes up, and pumps each
CBL2CAPChannel with blocking stream I/O on two threads. io_radio gains
what that needs: a blocking delivery for a reader on its own thread,
since a dropped chunk of a byte stream breaks its framing, and a RadioIo
that keeps its radio alive across Bluetooth toggles.

macOS assigns the listener PSM and cannot advertise it, so a Mac serves
it over GATT (Apple's L2CAP PSM characteristic, specified in psm.rs) and
reads a peer's there when its advert carried none. Linux and Android
cannot dial a Mac; the Mac's own dial forms the link, which the link
arbitration admits. CoreBluetooth hides link addresses, so a peer's
identifier is hashed into a stable locally administered BleAddr, and a
configured hci0/AA:... peer cannot be dialled from a Mac.

The fips binary embeds an Info.plist with a Bluetooth usage description,
without which CoreBluetooth kills the process. The Mac has one radio, so
more than one ble instance is a config error there.

Platform-neutral parts (dial PSM decision, address mapping, advert
throttle, stream pumps) are tested on every host; two ignored tests
exercise real hardware.
2026-10-13 20:56:07 +00:00

80 lines
3.2 KiB
Rust

use std::process::Command;
fn main() {
// Git commit hash (short)
let git_hash = Command::new("git")
.args(["rev-parse", "--short=10", "HEAD"])
.output()
.ok()
.filter(|o| o.status.success())
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
.unwrap_or_default();
println!("cargo:rustc-env=FIPS_GIT_HASH={git_hash}");
// Dirty working tree
let dirty = Command::new("git")
.args(["status", "--porcelain"])
.output()
.ok()
.filter(|o| o.status.success())
.map(|o| !o.stdout.is_empty())
.unwrap_or(false);
if dirty {
println!("cargo:rustc-env=FIPS_GIT_DIRTY=-dirty");
} else {
println!("cargo:rustc-env=FIPS_GIT_DIRTY=");
}
// Build target triple
if let Ok(target) = std::env::var("TARGET") {
println!("cargo:rustc-env=FIPS_TARGET={target}");
}
// Rebuild when commits change
println!("cargo:rerun-if-changed=.git/HEAD");
println!("cargo:rerun-if-changed=.git/refs/");
// Support reproducible builds (Debian packaging)
println!("cargo:rerun-if-env-changed=SOURCE_DATE_EPOCH");
// bluer/BlueZ is glibc-linux only: musl cross-compiles (OpenWrt) can't
// satisfy libdbus-sys's pkg-config cross-compile requirement, and musl
// router targets don't run BlueZ by default anyway.
println!("cargo:rustc-check-cfg=cfg(bluer_available)");
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default();
let bluer_available = target_os == "linux" && target_env != "musl";
if bluer_available {
println!("cargo:rustc-cfg=bluer_available");
}
// Whether the BLE transport is compiled at all.
//
// This is the set of platforms that have a concrete `BleIo` backend, not
// the set that could plausibly have one. A platform listed here with no
// backend behind it does not get "BLE, degraded" — it gets an in-memory
// transport that starts, reports itself up and never peers, with no error
// anywhere. Add a platform here only in the same change that adds its
// backend; `transport::ble` carries a compile-time tripwire that refuses
// a build where the two disagree.
println!("cargo:rustc-check-cfg=cfg(ble_available)");
if bluer_available || target_os == "android" || target_os == "macos" {
println!("cargo:rustc-cfg=ble_available");
}
// CoreBluetooth is privacy-gated (TCC). A process that touches it without
// an `NSBluetoothAlwaysUsageDescription` in its Info.plist is killed on
// the spot, and a bare executable has no bundle to carry one — so embed
// the plist in the binary's `__TEXT,__info_plist` section, where the OS
// looks for an unbundled tool's.
if target_os == "macos" {
let plist = std::path::Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap())
.join("packaging/macos/Info.plist");
println!("cargo:rerun-if-changed={}", plist.display());
println!(
"cargo:rustc-link-arg-bin=fips=-Wl,-sectcreate,__TEXT,__info_plist,{}",
plist.display()
);
}
}