Skip to main content

audio_plugin_bsd/
lib.rs

1//! Dynamic `.so` audio-plugin loader with ABI verification and FreeBSD
2//! Capsicum/`pdfork` per-process sandboxing for real-time audio in Rust.
3//!
4//! This crate loads third-party audio plugins compiled as `cdylib`
5//! (`.so` / `.dylib` / `.dll`) shared objects, verifies their ABI against the
6//! host, instantiates them behind a stable `extern "C"` entry point, and — on
7//! FreeBSD — confines each plugin to a Capsicum capability sandbox
8//! (optionally one isolated child process per plugin via `pdfork`).
9//!
10//! # Wire ABI
11//!
12//! Plugins do **not** cross the `.so` boundary as Rust trait objects
13//! (`Box<dyn AudioNode>`). A Rust trait object's layout and vtable are defined
14//! by the *compiling* toolchain and are **not** ABI-stable across different
15//! rustc or dependency versions; casting a foreign `.so`'s pointer to a Rust
16//! trait object is undefined behaviour. Instead, every plugin exposes plain
17//! `extern "C"` entry symbols:
18//!
19//! - `audio_plugin_abi_magic()  -> u32` — must equal
20//!   [`AUDIO_PLUGIN_ABI_MAGIC`] (`0x41504C47`, ASCII `"APLG"`).
21//! - `audio_plugin_abi_version() -> u32` — encoded via
22//!   [`encode_abi_version`]; the host accepts any plugin whose **major**
23//!   version matches its own (see [`is_abi_compatible`]).
24//! - `audio_plugin_metadata()` and `audio_plugin_create()` — identity and an
25//!   opaque handle consumed by the loader (follow-up task).
26//!
27//! The host never reinterpret-casts the returned pointer as a Rust trait
28//! object; it only calls further `extern "C"` functions on it. See [`abi`]
29//! for the version-encoding and compatibility rules.
30//!
31//! # Security warning — untrusted shared objects
32//!
33//! Loading a `.so` executes its initialisation code in the host process.
34//! **Never load a plugin you do not fully trust** unless it is confined. On
35//! FreeBSD this crate confines plugins with Capsicum; on other platforms the
36//! sandbox is a no-op and **untrusted plugins must not be loaded at all**.
37//!
38//! # Platform notes
39//!
40//! The `capsicum` and `pdfork` dependencies are gated behind
41//! `cfg(target_os = "freebsd")` and are **not** compiled on Linux, macOS, or
42//! Windows. On non-FreeBSD targets this crate still compiles and exposes the
43//! ABI / error / metadata types, but the loader's sandboxing is inactive.
44//!
45//! # IPC protocol
46//!
47//! Host↔plugin communication uses a simple binary protocol defined in
48//! [`proto`]. Every message is a single tag byte followed by a fixed-layout
49//! payload (u32 little-endian integers and length-prefixed UTF-8 strings).
50//! All decode functions are panic-free; malformed input returns
51//! [`PluginError::Sandbox`].
52//!
53//! # Sandbox configuration
54//!
55//! [`sandbox`] exposes [`IsolationLevel`] and [`SandboxConfig`]
56//! for controlling per-plugin confinement. On non-FreeBSD platforms only
57//! [`IsolationLevel::None`] is available.
58//!
59//! # Per-plugin process isolation
60//!
61//! On FreeBSD, [`isolation`] exposes [`spawn_isolated`], which runs
62//! each plugin in a dedicated `pdfork` child process confined to a Capsicum
63//! capability sandbox (`cap_enter`). Host↔plugin communication uses the
64//! length-prefixed [`proto`] messages over a Unix-domain socketpair. On
65//! non-FreeBSD targets [`spawn_isolated`] always returns
66//! [`PluginError::Sandbox`]. See [`isolation`] for the 0.1.0 control-loop
67//! scope and the real-time audio follow-up.
68//!
69//! # Real-time safety boundary
70//!
71//! The loader, ABI check, sandbox setup, and plugin instantiation all run on a
72//! **non-RT** (setup) thread. Code on the RT audio thread must only call into
73//! an already-instantiated plugin's `process` path (provided by the adapter in
74//! a follow-up task) and must obey the RT contract documented in
75//! `audio-core-bsd`.
76
77#![cfg_attr(docsrs, feature(doc_cfg))]
78#![warn(missing_docs)]
79#![warn(clippy::all, clippy::pedantic)]
80
81pub mod abi;
82pub mod error;
83pub mod isolation;
84pub mod loader;
85pub mod metadata;
86pub mod plugin;
87pub mod proto;
88pub mod sandbox;
89pub mod symbols;
90
91pub use abi::{
92    abi_major, abi_minor, encode_abi_version, is_abi_compatible, AbiVersion,
93    AUDIO_PLUGIN_ABI_MAGIC, AUDIO_PLUGIN_ABI_VERSION,
94};
95pub use error::{PluginError, Result};
96pub use isolation::{spawn_isolated, SandboxedProcess};
97pub use loader::{LoadedPlugin, PluginId, PluginLoader};
98pub use metadata::PluginMetadata;
99pub use plugin::Plugin;
100pub use proto::{
101    decode_request, decode_response, encode_request, encode_response, PluginMetadataPayload,
102    Request, Response,
103};
104pub use sandbox::{IsolationLevel, SandboxConfig};
105pub use symbols::{
106    AbiMagicFn, AbiVersionFn, CreateFn, DestroyFn, MetadataFn, RawPluginMetadata,
107    AUDIO_PLUGIN_ABI_MAGIC_SYMBOL, AUDIO_PLUGIN_ABI_VERSION_SYMBOL, AUDIO_PLUGIN_CREATE_SYMBOL,
108    AUDIO_PLUGIN_DESTROY_SYMBOL, AUDIO_PLUGIN_METADATA_SYMBOL,
109};